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

Unified Diff: net/sdch/sdch_owner.cc

Issue 901303002: Make SDCH dictionaries persistent across browser restart. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: More fixes Created 5 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: net/sdch/sdch_owner.cc
diff --git a/net/sdch/sdch_owner.cc b/net/sdch/sdch_owner.cc
index b0e440569646d0aa8ebd30d2a9ea05297a766b9c..aa57b491834c151bcfc3aae99ba1f3ae22a35a72 100644
--- a/net/sdch/sdch_owner.cc
+++ b/net/sdch/sdch_owner.cc
@@ -5,11 +5,17 @@
#include "net/sdch/sdch_owner.h"
#include "base/bind.h"
+#include "base/logging.h"
#include "base/metrics/histogram_macros.h"
+#include "base/prefs/persistent_pref_store.h"
+#include "base/prefs/value_map_pref_store.h"
#include "base/time/default_clock.h"
+#include "base/values.h"
#include "net/base/sdch_manager.h"
#include "net/base/sdch_net_log_params.h"
+namespace net {
+
namespace {
enum DictionaryFate {
@@ -26,8 +32,9 @@ enum DictionaryFate {
// A successful fetch was refused by the SdchManager.
DICTIONARY_FATE_FETCH_MANAGER_REFUSED = 4,
- // A dictionary was successfully added.
- DICTIONARY_FATE_ADDED = 5,
+ // A dictionary was successfully added based on
+ // a Get-Dictionary header in a response.
+ DICTIONARY_FATE_ADD_RESPONSE_TRIGGERED = 5,
// A dictionary was evicted by an incoming dict.
DICTIONARY_FATE_EVICT_FOR_DICT = 6,
@@ -38,13 +45,45 @@ enum DictionaryFate {
// A dictionary was evicted on destruction.
DICTIONARY_FATE_EVICT_FOR_DESTRUCTION = 8,
- DICTIONARY_FATE_MAX = 9
+ // A dictionary was successfully added based on
+ // persistence from a previous browser revision.
+ DICTIONARY_FATE_ADD_PERSISTENCE_TRIGGERED = 9,
+
+ // A dictionary was unloaded on destruction, but is still present on disk.
+ DICTIONARY_FATE_UNLOAD_FOR_DESTRUCTION = 10,
+
+ DICTIONARY_FATE_MAX = 11
+};
+
+enum PersistenceFailureReason {
+ // File didn't exist; is being created.
+ PERSISTENCE_FAILURE_REASON_NO_FILE = 1,
+
+ // Error reading in information, but should be able to write.
+ PERSISTENCE_FAILURE_REASON_READ_FAILED = 2,
+
+ // Error leading to abort on attempted persistence.
+ PERSISTENCE_FAILURE_REASON_WRITE_FAILED = 3,
+
+ PERSISTENCE_FAILURE_REASON_MAX = 4
};
+// Dictionaries that haven't been touched in 24 hours may be evicted
+// to make room for new dictionaries.
+const int kFreshnessLifetimeHours = 24;
+
+// Dictionaries that have never been used only stay fresh for one hour.
+const int kNeverUsedFreshnessLifetimeHours = 1;
+
void RecordDictionaryFate(enum DictionaryFate fate) {
UMA_HISTOGRAM_ENUMERATION("Sdch3.DictionaryFate", fate, DICTIONARY_FATE_MAX);
}
+void RecordPersistenceFailure(PersistenceFailureReason failure_reason) {
+ UMA_HISTOGRAM_ENUMERATION("Sdch3.PersistenceFailureReason", failure_reason,
+ PERSISTENCE_FAILURE_REASON_MAX);
+}
+
void RecordDictionaryEviction(int use_count, DictionaryFate fate) {
DCHECK(fate == DICTIONARY_FATE_EVICT_FOR_DICT ||
fate == DICTIONARY_FATE_EVICT_FOR_MEMORY ||
@@ -54,9 +93,163 @@ void RecordDictionaryEviction(int use_count, DictionaryFate fate) {
RecordDictionaryFate(fate);
}
-} // namespace
+void RecordDictionaryUnload(int use_count) {
Randy Smith (Not in Mondays) 2015/02/16 01:10:05 nit: Why the duplication with the above? I'd thin
Elly Fong-Jones 2015/02/17 20:35:29 Because I thought of Unloads as distinct from Evic
Randy Smith (Not in Mondays) 2015/02/17 20:49:36 Ah, good catch. That makes sense. I'm still not
+ UMA_HISTOGRAM_COUNTS_100("Sdch3.DictionaryUseCount", 0);
+ RecordDictionaryFate(DICTIONARY_FATE_UNLOAD_FOR_DESTRUCTION);
+}
-namespace net {
+// Schema specifications and access routines.
+
+// The persistent prefs store is conceptually shared with any other network
+// stack systems that want to persist data over browser restarts, and so
+// use of it must be namespace restricted.
+// Schema:
+// pref_store_->GetValue(kPreferenceName) -> Dictionary {
+// 'version' -> 1 [int]
+// 'dictionaries' -> Dictionary {
+// server_hash -> {
+// 'url' -> URL [string]
+// 'last_used' -> seconds since unix epoch [double]
+// 'use_count' -> use count [int]
+// 'size' -> size [int]
+// }
+// }
+const char kPreferenceName[] = "SDCH";
+const char kVersionKey[] = "version";
+const char kDictionariesKey[] = "dictionaries";
+const char kDictionaryUrlKey[] = "url";
+const char kDictionaryLastUsedKey[] = "last_used";
+const char kDictionaryUseCountKey[] = "use_count";
+const char kDictionarySizeKey[] = "size";
+
+const int kVersion = 1;
+
+// This function returns store[kPreferenceName/kDictionariesKey]. The caller
+// is responsible for making sure any needed calls to
+// |store->ReportValueChanged()| occur.
+base::DictionaryValue* GetPersistentStoreDictionaryMap(
+ WriteablePrefStore* store) {
+ base::Value* result = nullptr;
+ bool success = store->GetMutableValue(kPreferenceName, &result);
+ DCHECK(success);
+
+ base::DictionaryValue* preference_dictionary = nullptr;
+ success = result->GetAsDictionary(&preference_dictionary);
+ DCHECK(success);
+ DCHECK(preference_dictionary);
+
+ base::DictionaryValue* dictionary_list_dictionary = nullptr;
+ success = preference_dictionary->GetDictionary(kDictionariesKey,
+ &dictionary_list_dictionary);
+ DCHECK(success);
+ DCHECK(dictionary_list_dictionary);
+
+ return dictionary_list_dictionary;
+}
+
+// This function initializes a pref store with an empty version of the
+// above schema, removing anything previously in the store under
+// kPreferenceName.
+void InitializePrefStore(WriteablePrefStore* store) {
+ base::DictionaryValue* empty_store(new base::DictionaryValue);
+ empty_store->SetInteger(kVersionKey, kVersion);
+ empty_store->Set(kDictionariesKey,
+ make_scoped_ptr(new base::DictionaryValue));
+ store->SetValue(kPreferenceName, empty_store);
+}
+
+// A class to allow iteration over all dictionaries in the pref store, and
+// easy lookup of the information associated with those dictionaries.
+// Note that this is an "Iterator" in the same sense (and for the same
+// reasons) that base::Dictionary::Iterator is an iterator--it allows
+// iterating over all the dictionaries in the preference store, but it
+// does not allow use as an STL iterator because the container it
+// is iterating over does not export begin()/end() methods.
+class DictionaryPreferenceIterator {
+ public:
+ explicit DictionaryPreferenceIterator(WriteablePrefStore* pref_store);
+
+ bool IsAtEnd() const;
+ void Advance();
+
+ const std::string& server_hash() const { return server_hash_; }
+ const GURL url() const { return url_; }
+ base::Time last_used() const { return last_used_; }
+ int use_count() const { return use_count_; }
+ int size() const { return size_; }
+
+ private:
+ void LoadDictionaryOrDie();
+
+ std::string server_hash_;
+ GURL url_;
+ base::Time last_used_;
+ int use_count_;
+ int size_;
+
+ base::DictionaryValue::Iterator dictionary_iterator_;
+};
+
+DictionaryPreferenceIterator::DictionaryPreferenceIterator(
+ WriteablePrefStore* pref_store)
+ : dictionary_iterator_(*GetPersistentStoreDictionaryMap(pref_store)) {
+ if (!IsAtEnd())
+ LoadDictionaryOrDie();
+}
+
+bool DictionaryPreferenceIterator::IsAtEnd() const {
+ return dictionary_iterator_.IsAtEnd();
+}
+
+void DictionaryPreferenceIterator::Advance() {
+ dictionary_iterator_.Advance();
+ if (!IsAtEnd())
+ LoadDictionaryOrDie();
+}
+
+void DictionaryPreferenceIterator::LoadDictionaryOrDie() {
+ double last_used_seconds_from_epoch;
+ const base::DictionaryValue* dict = nullptr;
+ bool success =
+ dictionary_iterator_.value().GetAsDictionary(&dict);
+ DCHECK(success);
+
+ server_hash_ = dictionary_iterator_.key();
+
+ std::string url_spec;
+ success = dict->GetString(kDictionaryUrlKey, &url_spec);
+ DCHECK(success);
+ url_ = GURL(url_spec);
+
+ success = dict->GetDouble(kDictionaryLastUsedKey,
+ &last_used_seconds_from_epoch);
+ DCHECK(success);
+ last_used_ = base::Time::FromDoubleT(last_used_seconds_from_epoch);
+
+ success = dict->GetInteger(kDictionaryUseCountKey, &use_count_);
+ DCHECK(success);
+
+ success = dict->GetInteger(kDictionarySizeKey, &size_);
+ DCHECK(success);
+}
+
+// Triggers a ReportValueChanged() on the specified WriteablePrefStore
+// when the object goes out of scope.
+class ScopedPrefNotifier {
+ public:
+ // Caller must guarantee lifetime of |*pref_store| exceeds the
+ // lifetime of this object.
+ ScopedPrefNotifier(WriteablePrefStore* pref_store)
+ : pref_store_(pref_store) {}
+ ~ScopedPrefNotifier() { pref_store_->ReportValueChanged(kPreferenceName); }
+
+ private:
+ WriteablePrefStore* pref_store_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedPrefNotifier);
+};
+
+} // namespace
// Adjust SDCH limits downwards for mobile.
#if defined(OS_ANDROID) || defined(OS_IOS)
@@ -73,19 +266,16 @@ const size_t SdchOwner::kMaxTotalDictionarySize = 20 * 1000 * 1000;
// amount of space available in storage.
const size_t SdchOwner::kMinSpaceForDictionaryFetch = 50 * 1000;
-SdchOwner::SdchOwner(net::SdchManager* sdch_manager,
- net::URLRequestContext* context)
+SdchOwner::SdchOwner(SdchManager* sdch_manager, URLRequestContext* context)
: manager_(sdch_manager),
- fetcher_(context,
- base::Bind(&SdchOwner::OnDictionaryFetched,
- // Because |fetcher_| is owned by SdchOwner, the
- // SdchOwner object will be available for the lifetime
- // of |fetcher_|.
- base::Unretained(this))),
+ fetcher_(new SdchDictionaryFetcher(context)),
total_dictionary_bytes_(0),
clock_(new base::DefaultClock),
max_total_dictionary_size_(kMaxTotalDictionarySize),
min_space_for_dictionary_fetch_(kMinSpaceForDictionaryFetch),
+ in_memory_pref_store_(new ValueMapPrefStore()),
+ external_pref_store_(nullptr),
+ pref_store_(in_memory_pref_store_.get()),
memory_pressure_listener_(
base::Bind(&SdchOwner::OnMemoryPressure,
// Because |memory_pressure_listener_| is owned by
@@ -93,15 +283,34 @@ SdchOwner::SdchOwner(net::SdchManager* sdch_manager,
// for the lifetime of |memory_pressure_listener_|.
base::Unretained(this))) {
manager_->AddObserver(this);
+ InitializePrefStore(pref_store_);
}
SdchOwner::~SdchOwner() {
- for (auto it = local_dictionary_info_.begin();
- it != local_dictionary_info_.end(); ++it) {
- RecordDictionaryEviction(it->second.use_count,
- DICTIONARY_FATE_EVICT_FOR_DESTRUCTION);
+ for (DictionaryPreferenceIterator it(pref_store_); !it.IsAtEnd();
+ it.Advance()) {
+ if (IsPersistingDictionaries()) {
+ RecordDictionaryUnload(it.use_count());
+ } else {
+ RecordDictionaryEviction(it.use_count(),
+ DICTIONARY_FATE_EVICT_FOR_DESTRUCTION);
+ }
}
manager_->RemoveObserver(this);
+
+ // This object only observes the external store during loading,
+ // i.e. before it's made the default preferences store.
+ if (external_pref_store_)
+ external_pref_store_->RemoveObserver(this);
+}
+
+void SdchOwner::EnablePersistentStorage(PersistentPrefStore* pref_store) {
+ DCHECK(!external_pref_store_);
+ external_pref_store_ = pref_store;
+ external_pref_store_->AddObserver(this);
+
+ if (external_pref_store_->IsInitializationComplete())
+ OnInitializationCompleted(true);
}
void SdchOwner::SetMaxTotalDictionarySize(size_t max_total_dictionary_size) {
@@ -113,7 +322,9 @@ void SdchOwner::SetMinSpaceForDictionaryFetch(
min_space_for_dictionary_fetch_ = min_space_for_dictionary_fetch;
}
-void SdchOwner::OnDictionaryFetched(const std::string& dictionary_text,
+void SdchOwner::OnDictionaryFetched(base::Time last_used,
+ int use_count,
+ const std::string& dictionary_text,
const GURL& dictionary_url,
const net::BoundNetLog& net_log) {
struct DictionaryItem {
@@ -138,16 +349,27 @@ void SdchOwner::OnDictionaryFetched(const std::string& dictionary_text,
}
};
+ // Figure out if there is space for the incoming dictionary; evict
+ // stale dictionaries if needed to make space.
+
std::vector<DictionaryItem> stale_dictionary_list;
size_t recoverable_bytes = 0;
- base::Time stale_boundary(clock_->Now() - base::TimeDelta::FromDays(1));
- for (auto used_it = local_dictionary_info_.begin();
- used_it != local_dictionary_info_.end(); ++used_it) {
- if (used_it->second.last_used < stale_boundary) {
- stale_dictionary_list.push_back(
- DictionaryItem(used_it->second.last_used, used_it->first,
- used_it->second.use_count, used_it->second.size));
- recoverable_bytes += used_it->second.size;
+ base::Time now(clock_->Now());
+ // Dictionaries whose last used time is before |stale_boundary| are candidates
+ // for eviction if necessary.
+ base::Time stale_boundary(
+ now - base::TimeDelta::FromHours(kFreshnessLifetimeHours));
+ // Dictionaries that have never been used and are from before
+ // |never_used_stale_boundary| are candidates for eviction if necessary.
+ base::Time never_used_stale_boundary(
+ now - base::TimeDelta::FromHours(kNeverUsedFreshnessLifetimeHours));
+ for (DictionaryPreferenceIterator it(pref_store_); !it.IsAtEnd();
+ it.Advance()) {
+ if (it.last_used() < stale_boundary ||
+ (it.use_count() == 0 && it.last_used() < never_used_stale_boundary)) {
+ stale_dictionary_list.push_back(DictionaryItem(
+ it.last_used(), it.server_hash(), it.use_count(), it.size()));
+ recoverable_bytes += it.size();
}
}
@@ -161,21 +383,9 @@ void SdchOwner::OnDictionaryFetched(const std::string& dictionary_text,
return;
}
- // Evict from oldest to youngest until we have space.
- std::sort(stale_dictionary_list.begin(), stale_dictionary_list.end());
- size_t avail_bytes = max_total_dictionary_size_ - total_dictionary_bytes_;
- auto stale_it = stale_dictionary_list.begin();
- while (avail_bytes < dictionary_text.size() &&
- stale_it != stale_dictionary_list.end()) {
- manager_->RemoveSdchDictionary(stale_it->server_hash);
- RecordDictionaryEviction(stale_it->use_count,
- DICTIONARY_FATE_EVICT_FOR_DICT);
- local_dictionary_info_.erase(stale_it->server_hash);
- avail_bytes += stale_it->dictionary_size;
- ++stale_it;
- }
- DCHECK(avail_bytes >= dictionary_text.size());
-
+ // Add the new dictionary. This is done before removing the stale
+ // dictionaries so that no state change will occur if dictionary addition
+ // fails.
std::string server_hash;
net::SdchProblemCode rv = manager_->AddSdchDictionary(
dictionary_text, dictionary_url, &server_hash);
@@ -188,25 +398,93 @@ void SdchOwner::OnDictionaryFetched(const std::string& dictionary_text,
return;
}
- RecordDictionaryFate(DICTIONARY_FATE_ADDED);
+ base::DictionaryValue* pref_dictionary_map =
+ GetPersistentStoreDictionaryMap(pref_store_);
+ ScopedPrefNotifier scoped_pref_notifier(pref_store_);
+
+ // Remove the old dictionaries.
+ std::sort(stale_dictionary_list.begin(), stale_dictionary_list.end());
+ size_t avail_bytes = max_total_dictionary_size_ - total_dictionary_bytes_;
+ auto stale_it = stale_dictionary_list.begin();
+ while (avail_bytes < dictionary_text.size() &&
+ stale_it != stale_dictionary_list.end()) {
+ manager_->RemoveSdchDictionary(stale_it->server_hash);
+
+ DCHECK(pref_dictionary_map->HasKey(stale_it->server_hash));
+ bool success = pref_dictionary_map->RemoveWithoutPathExpansion(
+ stale_it->server_hash, nullptr);
+ DCHECK(success);
+
+ avail_bytes += stale_it->dictionary_size;
+
+ RecordDictionaryEviction(stale_it->use_count,
+ DICTIONARY_FATE_EVICT_FOR_DICT);
+
+ ++stale_it;
+ }
+ DCHECK_GE(avail_bytes, dictionary_text.size());
+
+ RecordDictionaryFate(
+ // Distinguish between loads triggered by network responses and
+ // loads triggered by persistence.
+ last_used.is_null() ? DICTIONARY_FATE_ADD_RESPONSE_TRIGGERED
+ : DICTIONARY_FATE_ADD_PERSISTENCE_TRIGGERED);
+
+ // If a dictionary has never been used, its dictionary addition time
+ // is recorded as its last used time. Never used dictionaries are treated
+ // specially in the freshness logic.
+ if (last_used.is_null())
+ last_used = clock_->Now();
- DCHECK(local_dictionary_info_.end() ==
- local_dictionary_info_.find(server_hash));
total_dictionary_bytes_ += dictionary_text.size();
- local_dictionary_info_[server_hash] = DictionaryInfo(
- // Set the time last used to something to avoid thrashing, but not recent,
- // to avoid taking too much time/space with useless dictionaries/one-off
- // visits to web sites.
- clock_->Now() - base::TimeDelta::FromHours(23), dictionary_text.size());
+
+ // Record the addition in the pref store.
+ scoped_ptr<base::DictionaryValue> dictionary_description(
+ new base::DictionaryValue());
+ dictionary_description->SetString(kDictionaryUrlKey, dictionary_url.spec());
+ dictionary_description->SetDouble(kDictionaryLastUsedKey,
+ last_used.ToDoubleT());
+ dictionary_description->SetInteger(kDictionaryUseCountKey, use_count);
+ dictionary_description->SetInteger(kDictionarySizeKey,
+ dictionary_text.size());
+ pref_dictionary_map->Set(server_hash, dictionary_description.Pass());
}
void SdchOwner::OnDictionaryUsed(SdchManager* manager,
const std::string& server_hash) {
- auto it = local_dictionary_info_.find(server_hash);
- DCHECK(local_dictionary_info_.end() != it);
-
- it->second.last_used = clock_->Now();
- it->second.use_count++;
+ base::Time now(clock_->Now());
+ base::DictionaryValue* pref_dictionary_map =
+ GetPersistentStoreDictionaryMap(pref_store_);
+ ScopedPrefNotifier scoped_pref_notifier(pref_store_);
+
+ base::Value* value = nullptr;
+ bool success = pref_dictionary_map->Get(server_hash, &value);
+ DCHECK(success);
+ base::DictionaryValue* specific_dictionary_map = nullptr;
+ success = value->GetAsDictionary(&specific_dictionary_map);
+ DCHECK(success);
+
+ double last_used_seconds_since_epoch = 0.0;
+ success = specific_dictionary_map->GetDouble(kDictionaryLastUsedKey,
+ &last_used_seconds_since_epoch);
+ DCHECK(success);
+ int use_count = 0;
+ success =
+ specific_dictionary_map->GetInteger(kDictionaryUseCountKey, &use_count);
+ DCHECK(success);
+
+ base::TimeDelta time_since_last_used(now -
+ base::Time::FromDoubleT(last_used_seconds_since_epoch));
+
+ // TODO(rdsmith): Distinguish between "Never used" and "Actually not
+ // touched for 48 hours".
+ UMA_HISTOGRAM_CUSTOM_TIMES(
+ "Sdch3.UsageInterval",
+ use_count ? time_since_last_used : base::TimeDelta::FromHours(48),
+ base::TimeDelta(), base::TimeDelta::FromHours(48), 50);
+
+ specific_dictionary_map->SetDouble(kDictionaryLastUsedKey, now.ToDoubleT());
+ specific_dictionary_map->SetInteger(kDictionaryUseCountKey, use_count + 1);
}
void SdchOwner::OnGetDictionary(net::SdchManager* manager,
@@ -214,10 +492,10 @@ void SdchOwner::OnGetDictionary(net::SdchManager* manager,
const GURL& dictionary_url) {
base::Time stale_boundary(clock_->Now() - base::TimeDelta::FromDays(1));
size_t avail_bytes = 0;
- for (auto it = local_dictionary_info_.begin();
- it != local_dictionary_info_.end(); ++it) {
- if (it->second.last_used < stale_boundary)
- avail_bytes += it->second.size;
+ for (DictionaryPreferenceIterator it(pref_store_); !it.IsAtEnd();
+ it.Advance()) {
+ if (it.last_used() < stale_boundary)
+ avail_bytes += it.size();
}
// Don't initiate the fetch if we wouldn't be able to store any
@@ -234,27 +512,141 @@ void SdchOwner::OnGetDictionary(net::SdchManager* manager,
return;
}
- fetcher_.Schedule(dictionary_url);
+ fetcher_->Schedule(dictionary_url,
+ base::Bind(&SdchOwner::OnDictionaryFetched,
+ // SdchOwner will outlive its member variables.
+ base::Unretained(this), base::Time(), 0));
}
void SdchOwner::OnClearDictionaries(net::SdchManager* manager) {
total_dictionary_bytes_ = 0;
- local_dictionary_info_.clear();
- fetcher_.Cancel();
+ fetcher_->Cancel();
+
+ InitializePrefStore(pref_store_);
+}
+
+void SdchOwner::OnPrefValueChanged(const std::string& key) {
+}
+
+void SdchOwner::OnInitializationCompleted(bool succeeded) {
+ PersistentPrefStore::PrefReadError error =
+ external_pref_store_->GetReadError();
+ // Errors on load are self-correcting; if dictionaries were not
+ // persisted from the last instance of the browser, they will be
+ // faulted in by user action over time. However, if a load error
+ // means that the dictionary information won't be able to be persisted,
+ // the in memory pref store is left in place.
+ if (!succeeded) {
+ // Failure means a write failed, since read failures are recoverable.
+ DCHECK_NE(
+ error,
+ PersistentPrefStore::PREF_READ_ERROR_ASYNCHRONOUS_TASK_INCOMPLETE);
+ DCHECK_NE(error,
+ PersistentPrefStore::PREF_READ_ERROR_MAX_ENUM);
+
+ LOG(ERROR) << "Pref store write failed: " << error;
+ external_pref_store_->RemoveObserver(this);
+ external_pref_store_ = nullptr;
+ RecordPersistenceFailure(PERSISTENCE_FAILURE_REASON_WRITE_FAILED);
+ return;
+ }
+ switch (external_pref_store_->GetReadError()) {
+ case PersistentPrefStore::PREF_READ_ERROR_NONE:
+ break;
+
+ case PersistentPrefStore::PREF_READ_ERROR_NO_FILE:
+ // First time reading; the file will be created.
+ RecordPersistenceFailure(PERSISTENCE_FAILURE_REASON_NO_FILE);
+ break;
+
+ case PersistentPrefStore::PREF_READ_ERROR_JSON_PARSE:
+ case PersistentPrefStore::PREF_READ_ERROR_JSON_TYPE:
+ case PersistentPrefStore::PREF_READ_ERROR_FILE_OTHER:
+ case PersistentPrefStore::PREF_READ_ERROR_FILE_LOCKED:
+ case PersistentPrefStore::PREF_READ_ERROR_JSON_REPEAT:
+ case PersistentPrefStore::PREF_READ_ERROR_LEVELDB_IO:
+ case PersistentPrefStore::PREF_READ_ERROR_LEVELDB_CORRUPTION_READ_ONLY:
+ case PersistentPrefStore::PREF_READ_ERROR_LEVELDB_CORRUPTION:
+ RecordPersistenceFailure(PERSISTENCE_FAILURE_REASON_READ_FAILED);
+ break;
+
+ case PersistentPrefStore::PREF_READ_ERROR_ACCESS_DENIED:
+ case PersistentPrefStore::PREF_READ_ERROR_FILE_NOT_SPECIFIED:
+ case PersistentPrefStore::PREF_READ_ERROR_ASYNCHRONOUS_TASK_INCOMPLETE:
+ case PersistentPrefStore::PREF_READ_ERROR_MAX_ENUM:
+ // Shouldn't ever happen. ACCESS_DENIED and FILE_NOT_SPECIFIED should
+ // imply !succeeded, and TASK_INCOMPLETE should never be delivered.
+ NOTREACHED();
+ break;
+ }
+
+
+ // TODO(rdsmith): Implement versioning.
Randy Smith (Not in Mondays) 2015/02/16 01:10:05 This CL actually implements probably the best vers
Elly Fong-Jones 2015/02/17 20:35:29 Done.
+
+ // Load in what was stored before chrome exited previously.
+ const base::Value* sdch_persistence_value = nullptr;
+ const base::DictionaryValue* sdch_persistence_dictionary = nullptr;
+
+ // The GetPersistentStore() routine above assumes data formatted
+ // according to the schema described at the top of this file. Since
+ // this data comes from disk, to avoid disk corruption resulting in
+ // persistent chrome errors this code avoids those assupmtions.
+ if (external_pref_store_->GetValue(kPreferenceName,
+ &sdch_persistence_value) &&
+ sdch_persistence_value->GetAsDictionary(&sdch_persistence_dictionary)) {
+ SchedulePersistedDictionaryLoads(*sdch_persistence_dictionary);
+ }
+
+ // Reset the persistent store and update it with the accumulated
+ // information from the local store.
+ InitializePrefStore(external_pref_store_);
+
+ ScopedPrefNotifier scoped_pref_notifier(external_pref_store_);
+ GetPersistentStoreDictionaryMap(external_pref_store_)
+ ->Swap(GetPersistentStoreDictionaryMap(in_memory_pref_store_.get()));
+
+ // This object can stop waiting on (i.e. observing) the external preference
+ // store and switch over to using it as the primary preference store.
+ pref_store_ = external_pref_store_;
+ external_pref_store_->RemoveObserver(this);
+ external_pref_store_ = nullptr;
+ in_memory_pref_store_ = nullptr;
}
void SdchOwner::SetClockForTesting(scoped_ptr<base::Clock> clock) {
clock_ = clock.Pass();
}
+int SdchOwner::GetDictionaryCount() const {
+ int count = 0;
+ for (DictionaryPreferenceIterator it(pref_store_); !it.IsAtEnd();
+ it.Advance()) {
+ count++;
+ }
+ return count;
+}
+
+bool SdchOwner::HasDictionaryFrom(const GURL& url) const {
+ for (DictionaryPreferenceIterator it(pref_store_); !it.IsAtEnd();
+ it.Advance()) {
+ if (it.url() == url)
+ return true;
+ }
+ return false;
+}
+
+void SdchOwner::SetFetcherForTesting(
+ scoped_ptr<SdchDictionaryFetcher> fetcher) {
+ fetcher_.reset(fetcher.release());
+}
+
void SdchOwner::OnMemoryPressure(
base::MemoryPressureListener::MemoryPressureLevel level) {
DCHECK_NE(base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE, level);
- for (auto it = local_dictionary_info_.begin();
- it != local_dictionary_info_.end(); ++it) {
- RecordDictionaryEviction(it->second.use_count,
- DICTIONARY_FATE_EVICT_FOR_MEMORY);
+ for (DictionaryPreferenceIterator it(pref_store_); !it.IsAtEnd();
+ it.Advance()) {
+ RecordDictionaryEviction(it.use_count(), DICTIONARY_FATE_EVICT_FOR_MEMORY);
}
// TODO(rdsmith): Make a distinction between moderate and critical
@@ -262,4 +654,56 @@ void SdchOwner::OnMemoryPressure(
manager_->ClearData();
}
+bool SdchOwner::SchedulePersistedDictionaryLoads(
+ const base::DictionaryValue& persisted_info) {
+ // Any schema error will result in dropping the persisted info.
+ int version = 0;
+ if (!persisted_info.GetInteger(kVersionKey, &version))
+ return false;
+
+ // Any version mismatch will result in dropping the persisted info;
+ // it will be faulted in at small performance cost as URLs using
+ // dictionaries for encoding are visited.
+ if (version != kVersion)
+ return false;
+
+ const base::DictionaryValue* dictionary_set = nullptr;
+ if (!persisted_info.GetDictionary(kDictionariesKey, &dictionary_set))
+ return false;
+
+ // Any formatting error will result in skipping that particular
+ // dictionary.
+ for (base::DictionaryValue::Iterator dict_it(*dictionary_set);
+ !dict_it.IsAtEnd(); dict_it.Advance()) {
+ const base::DictionaryValue* dict_info = nullptr;
+ if (!dict_it.value().GetAsDictionary(&dict_info))
+ continue;
+
+ std::string url_string;
+ if (!dict_info->GetString(kDictionaryUrlKey, &url_string))
+ continue;
+ GURL dict_url(url_string);
+
+ double last_used;
+ if (!dict_info->GetDouble(kDictionaryLastUsedKey, &last_used))
+ continue;
+
+ int use_count;
+ if (!dict_info->GetInteger(kDictionaryUseCountKey, &use_count))
+ continue;
+
+ fetcher_->ScheduleReload(
+ dict_url, base::Bind(&SdchOwner::OnDictionaryFetched,
+ // SdchOwner will outlive its member variables.
+ base::Unretained(this),
+ base::Time::FromDoubleT(last_used), use_count));
+ }
+
+ return true;
+}
+
+bool SdchOwner::IsPersistingDictionaries() const {
+ return in_memory_pref_store_.get() != nullptr;
+}
+
} // namespace net

Powered by Google App Engine
This is Rietveld 408576698