| Index: net/sdch/sdch_owner.cc
|
| diff --git a/net/sdch/sdch_owner.cc b/net/sdch/sdch_owner.cc
|
| index 1396c79a7cb463ec717fd2b7715f01bb37c405d4..c92e2c774b84629edc6cd6c02d59bf7a89a5f4d1 100644
|
| --- a/net/sdch/sdch_owner.cc
|
| +++ b/net/sdch/sdch_owner.cc
|
| @@ -6,12 +6,18 @@
|
|
|
| #include "base/bind.h"
|
| #include "base/debug/alias.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/strings/string_util.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 {
|
| @@ -28,8 +34,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,
|
| @@ -40,25 +47,209 @@ 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 RecordDictionaryEviction(int use_count, DictionaryFate fate) {
|
| +void RecordPersistenceFailure(PersistenceFailureReason failure_reason) {
|
| + UMA_HISTOGRAM_ENUMERATION("Sdch3.PersistenceFailureReason", failure_reason,
|
| + PERSISTENCE_FAILURE_REASON_MAX);
|
| +}
|
| +
|
| +void RecordDictionaryEvictionOrUnload(int use_count, DictionaryFate fate) {
|
| DCHECK(fate == DICTIONARY_FATE_EVICT_FOR_DICT ||
|
| fate == DICTIONARY_FATE_EVICT_FOR_MEMORY ||
|
| - fate == DICTIONARY_FATE_EVICT_FOR_DESTRUCTION);
|
| + fate == DICTIONARY_FATE_EVICT_FOR_DESTRUCTION ||
|
| + fate == DICTIONARY_FATE_UNLOAD_FOR_DESTRUCTION);
|
|
|
| UMA_HISTOGRAM_COUNTS_100("Sdch3.DictionaryUseCount", use_count);
|
| RecordDictionaryFate(fate);
|
| }
|
|
|
| -} // namespace
|
| +// 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;
|
| +}
|
|
|
| -namespace net {
|
| +// 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. This iterator can
|
| +// only be safely used on sanitized pref stores that are known to conform to the
|
| +// pref store schema.
|
| +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)
|
| @@ -75,31 +266,28 @@ 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)
|
| + min_space_for_dictionary_fetch_(kMinSpaceForDictionaryFetch),
|
| // TODO(rmcilroy) Add back memory_pressure_listener_ when
|
| // http://crbug.com/447208 is fixed
|
| #if defined(OS_CHROMEOS)
|
| // For debugging http://crbug.com/454198; remove when resolved.
|
| - , destroyed_(0)
|
| + destroyed_(0),
|
| #endif
|
| - {
|
| + in_memory_pref_store_(new ValueMapPrefStore()),
|
| + external_pref_store_(nullptr),
|
| + pref_store_(in_memory_pref_store_.get()) {
|
| #if defined(OS_CHROMEOS)
|
| // For debugging http://crbug.com/454198; remove when resolved.
|
| CHECK(clock_.get());
|
| #endif
|
| manager_->AddObserver(this);
|
| + InitializePrefStore(pref_store_);
|
| }
|
|
|
| SdchOwner::~SdchOwner() {
|
| @@ -110,17 +298,35 @@ SdchOwner::~SdchOwner() {
|
| clock_.reset();
|
| #endif
|
|
|
| - 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()) {
|
| + int new_uses = it.use_count() - use_counts_at_load_[it.server_hash()];
|
| + DictionaryFate fate = IsPersistingDictionaries() ?
|
| + DICTIONARY_FATE_UNLOAD_FOR_DESTRUCTION :
|
| + DICTIONARY_FATE_EVICT_FOR_DESTRUCTION;
|
| + RecordDictionaryEvictionOrUnload(new_uses, fate);
|
| }
|
| 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);
|
| +
|
| #if defined(OS_CHROMEOS)
|
| destroyed_ = 0xdeadbeef;
|
| #endif
|
| }
|
|
|
| +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) {
|
| max_total_dictionary_size_ = max_total_dictionary_size;
|
| }
|
| @@ -130,7 +336,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 {
|
| @@ -161,16 +369,27 @@ void SdchOwner::OnDictionaryFetched(const std::string& dictionary_text,
|
| CHECK(clock_.get());
|
| #endif
|
|
|
| + // 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();
|
| }
|
| }
|
|
|
| @@ -190,21 +409,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);
|
| @@ -217,31 +424,105 @@ 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;
|
| +
|
| + int new_uses = stale_it->use_count -
|
| + use_counts_at_load_[stale_it->server_hash];
|
| + RecordDictionaryEvictionOrUnload(new_uses,
|
| + 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());
|
|
|
| #if defined(OS_CHROMEOS)
|
| // For debugging http://crbug.com/454198; remove when resolved.
|
| CHECK_EQ(0u, destroyed_);
|
| CHECK(clock_.get());
|
| #endif
|
| +
|
| + // 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);
|
| + 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);
|
| +
|
| + if (use_counts_at_load_.count(server_hash) == 0) {
|
| + use_counts_at_load_[server_hash] = use_count;
|
| + }
|
| +
|
| + base::TimeDelta time_since_last_used(now -
|
| + base::Time::FromDoubleT(last_used_seconds_since_epoch));
|
|
|
| - it->second.last_used = clock_->Now();
|
| - it->second.use_count++;
|
| + // 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,
|
| @@ -261,10 +542,10 @@ void SdchOwner::OnGetDictionary(net::SdchManager* manager,
|
|
|
| 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
|
| @@ -281,13 +562,103 @@ 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;
|
| + }
|
| +
|
| +
|
| + // 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) {
|
| @@ -300,14 +671,38 @@ void SdchOwner::SetClockForTesting(scoped_ptr<base::Clock> clock) {
|
| #endif
|
| }
|
|
|
| +int SdchOwner::GetDictionaryCountForTesting() const {
|
| + int count = 0;
|
| + for (DictionaryPreferenceIterator it(pref_store_); !it.IsAtEnd();
|
| + it.Advance()) {
|
| + count++;
|
| + }
|
| + return count;
|
| +}
|
| +
|
| +bool SdchOwner::HasDictionaryFromURLForTesting(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()) {
|
| + int new_uses = it.use_count() - use_counts_at_load_[it.server_hash()];
|
| + RecordDictionaryEvictionOrUnload(new_uses,
|
| + DICTIONARY_FATE_EVICT_FOR_MEMORY);
|
| }
|
|
|
| // TODO(rdsmith): Make a distinction between moderate and critical
|
| @@ -315,4 +710,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
|
|
|