diff options
author | rdsmith <rdsmith@chromium.org> | 2015-01-08 12:18:17 -0800 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2015-01-08 20:19:44 +0000 |
commit | a8a4e3c851d3e12ad27ac25493b6b690b867e39c (patch) | |
tree | 7022b0edeb8e2095afcd57873e0130e002876b55 /net/sdch | |
parent | 9742c5d33b5a2d7378997f7990e3fc31f06c3ee8 (diff) | |
download | chromium_src-a8a4e3c851d3e12ad27ac25493b6b690b867e39c.zip chromium_src-a8a4e3c851d3e12ad27ac25493b6b690b867e39c.tar.gz chromium_src-a8a4e3c851d3e12ad27ac25493b6b690b867e39c.tar.bz2 |
Add an eviction mechanism for SDCH dictionaries.
Implemented policy:
* If space is needed for a newly loaded dictionary, evict any dictionaries
that haven't been used in a day to make room, oldest first.
* If the system signals memory pressure, flush all dictionaries.
Also moved chrome_sdch_policy.* -> net/sdch/sdch_owner.*.
BUG=387883
BUG=374916
Review URL: https://codereview.chromium.org/841883002
Cr-Commit-Position: refs/heads/master@{#310584}
Diffstat (limited to 'net/sdch')
-rw-r--r-- | net/sdch/sdch_owner.cc | 265 | ||||
-rw-r--r-- | net/sdch/sdch_owner.h | 97 | ||||
-rw-r--r-- | net/sdch/sdch_owner_unittest.cc | 491 |
3 files changed, 853 insertions, 0 deletions
diff --git a/net/sdch/sdch_owner.cc b/net/sdch/sdch_owner.cc new file mode 100644 index 0000000..fc1c57b --- /dev/null +++ b/net/sdch/sdch_owner.cc @@ -0,0 +1,265 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/sdch/sdch_owner.h" + +#include "base/bind.h" +#include "base/metrics/histogram_macros.h" +#include "base/time/default_clock.h" +#include "net/base/sdch_manager.h" +#include "net/base/sdch_net_log_params.h" + +namespace { + +enum DictionaryFate { + // A Get-Dictionary header wasn't acted on. + DICTIONARY_FATE_GET_IGNORED = 1, + + // A fetch was attempted, but failed. + // TODO(rdsmith): Actually record this case. + DICTIONARY_FATE_FETCH_FAILED = 2, + + // A successful fetch was dropped on the floor, no space. + DICTIONARY_FATE_FETCH_IGNORED_NO_SPACE = 3, + + // 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 evicted by an incoming dict. + DICTIONARY_FATE_EVICT_FOR_DICT = 6, + + // A dictionary was evicted by memory pressure. + DICTIONARY_FATE_EVICT_FOR_MEMORY = 7, + + // A dictionary was evicted on destruction. + DICTIONARY_FATE_EVICT_FOR_DESTRUCTION = 8, + + DICTIONARY_FATE_MAX = 9 +}; + +void RecordDictionaryFate(enum DictionaryFate fate) { + UMA_HISTOGRAM_ENUMERATION("Sdch3.DictionaryFate", fate, DICTIONARY_FATE_MAX); +} + +void RecordDictionaryEviction(int use_count, DictionaryFate fate) { + DCHECK(fate == DICTIONARY_FATE_EVICT_FOR_DICT || + fate == DICTIONARY_FATE_EVICT_FOR_MEMORY || + fate == DICTIONARY_FATE_EVICT_FOR_DESTRUCTION); + + UMA_HISTOGRAM_COUNTS_100("Sdch3.DictionaryUseCount", use_count); + RecordDictionaryFate(fate); +} + +} // namespace + +namespace net { + +// Adjust SDCH limits downwards for mobile. +#if defined(OS_ANDROID) || defined(OS_IOS) +// static +const size_t SdchOwner::kMaxTotalDictionarySize = 500 * 1000; +#else +// static +const size_t SdchOwner::kMaxTotalDictionarySize = 20 * 1000 * 1000; +#endif + +// Somewhat arbitrary, but we assume a dictionary smaller than +// 50K isn't going to do anyone any good. Note that this still doesn't +// prevent download and addition unless there is less than this +// amount of space available in storage. +const size_t SdchOwner::kMinSpaceForDictionaryFetch = 50 * 1000; + +SdchOwner::SdchOwner(net::SdchManager* sdch_manager, + net::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))), + total_dictionary_bytes_(0), + clock_(new base::DefaultClock), + max_total_dictionary_size_(kMaxTotalDictionarySize), + min_space_for_dictionary_fetch_(kMinSpaceForDictionaryFetch), + memory_pressure_listener_( + base::Bind(&SdchOwner::OnMemoryPressure, + // Because |memory_pressure_listener_| is owned by + // SdchOwner, the SdchOwner object will be available + // for the lifetime of |memory_pressure_listener_|. + base::Unretained(this))) { + manager_->AddObserver(this); +} + +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); + } + manager_->RemoveObserver(this); +} + +void SdchOwner::SetMaxTotalDictionarySize(size_t max_total_dictionary_size) { + max_total_dictionary_size_ = max_total_dictionary_size; +} + +void SdchOwner::SetMinSpaceForDictionaryFetch( + size_t min_space_for_dictionary_fetch) { + min_space_for_dictionary_fetch_ = min_space_for_dictionary_fetch; +} + +void SdchOwner::OnDictionaryFetched(const std::string& dictionary_text, + const GURL& dictionary_url, + const net::BoundNetLog& net_log) { + struct DictionaryItem { + base::Time last_used; + std::string server_hash; + int use_count; + size_t dictionary_size; + + DictionaryItem() : use_count(0), dictionary_size(0) {} + DictionaryItem(const base::Time& last_used, + const std::string& server_hash, + int use_count, + size_t dictionary_size) + : last_used(last_used), + server_hash(server_hash), + use_count(use_count), + dictionary_size(dictionary_size) {} + DictionaryItem(const DictionaryItem& rhs) = default; + DictionaryItem& operator=(const DictionaryItem& rhs) = default; + bool operator<(const DictionaryItem& rhs) const { + return last_used < rhs.last_used; + } + }; + + 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; + } + } + + if (total_dictionary_bytes_ + dictionary_text.size() - recoverable_bytes > + max_total_dictionary_size_) { + RecordDictionaryFate(DICTIONARY_FATE_FETCH_IGNORED_NO_SPACE); + net::SdchManager::SdchErrorRecovery(SDCH_DICTIONARY_NO_ROOM); + net_log.AddEvent(net::NetLog::TYPE_SDCH_DICTIONARY_ERROR, + base::Bind(&net::NetLogSdchDictionaryFetchProblemCallback, + SDCH_DICTIONARY_NO_ROOM, dictionary_url, true)); + 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()); + + std::string server_hash; + net::SdchProblemCode rv = manager_->AddSdchDictionary( + dictionary_text, dictionary_url, &server_hash); + if (rv != net::SDCH_OK) { + RecordDictionaryFate(DICTIONARY_FATE_FETCH_MANAGER_REFUSED); + net::SdchManager::SdchErrorRecovery(rv); + net_log.AddEvent(net::NetLog::TYPE_SDCH_DICTIONARY_ERROR, + base::Bind(&net::NetLogSdchDictionaryFetchProblemCallback, + rv, dictionary_url, true)); + return; + } + + RecordDictionaryFate(DICTIONARY_FATE_ADDED); + + 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()); +} + +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++; +} + +void SdchOwner::OnGetDictionary(net::SdchManager* manager, + const GURL& request_url, + 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; + } + + // Don't initiate the fetch if we wouldn't be able to store any + // reasonable dictionary. + // TODO(rdsmith): Maybe do a HEAD request to figure out how much + // storage we'd actually need? + if (max_total_dictionary_size_ < (total_dictionary_bytes_ - avail_bytes + + min_space_for_dictionary_fetch_)) { + RecordDictionaryFate(DICTIONARY_FATE_GET_IGNORED); + // TODO(rdsmith): Log a net-internals error. This requires + // SdchManager to forward the URLRequest that detected the + // Get-Dictionary header to its observers, which is tricky + // because SdchManager is layered underneath URLRequest. + return; + } + + fetcher_.Schedule(dictionary_url); +} + +void SdchOwner::OnClearDictionaries(net::SdchManager* manager) { + total_dictionary_bytes_ = 0; + local_dictionary_info_.clear(); + fetcher_.Cancel(); +} + +void SdchOwner::SetClockForTesting(scoped_ptr<base::Clock> clock) { + clock_ = clock.Pass(); +} + +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); + } + + // TODO(rdsmith): Make a distinction between moderate and critical + // memory pressure. + manager_->ClearData(); +} + +} // namespace net diff --git a/net/sdch/sdch_owner.h b/net/sdch/sdch_owner.h new file mode 100644 index 0000000..9620326 --- /dev/null +++ b/net/sdch/sdch_owner.h @@ -0,0 +1,97 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NET_SDCH_SDCH_OWNER_H_ +#define NET_SDCH_SDCH_OWNER_H_ + +#include <string> + +#include "base/memory/memory_pressure_listener.h" +#include "net/base/sdch_observer.h" +#include "net/url_request/sdch_dictionary_fetcher.h" + +class GURL; + +namespace base { +class Clock; +} + +namespace net { +class SdchManager; +class URLRequestContext; + +// This class owns the SDCH objects not owned as part of URLRequestContext, and +// exposes interface for setting SDCH policy. It should be instantiated by +// the net/ embedder. +// TODO(rdsmith): Implement dictionary prioritization. +class NET_EXPORT SdchOwner : public net::SdchObserver { + public: + static const size_t kMaxTotalDictionarySize; + static const size_t kMinSpaceForDictionaryFetch; + + // Consumer must guarantee that |sdch_manager| and |context| outlive + // this object. + SdchOwner(net::SdchManager* sdch_manager, net::URLRequestContext* context); + ~SdchOwner() override; + + // Defaults to kMaxTotalDictionarySize. + void SetMaxTotalDictionarySize(size_t max_total_dictionary_size); + + // Defaults to kMinSpaceForDictionaryFetch. + void SetMinSpaceForDictionaryFetch(size_t min_space_for_dictionary_fetch); + + // SdchObserver implementation. + void OnDictionaryUsed(SdchManager* manager, + const std::string& server_hash) override; + void OnGetDictionary(net::SdchManager* manager, + const GURL& request_url, + const GURL& dictionary_url) override; + void OnClearDictionaries(net::SdchManager* manager) override; + + // Implementation detail--this is the pathway through which the + // fetcher informs the SdchOwner that it's gotten the dictionary. + // Public for testing. + void OnDictionaryFetched(const std::string& dictionary_text, + const GURL& dictionary_url, + const net::BoundNetLog& net_log); + + void SetClockForTesting(scoped_ptr<base::Clock> clock); + + private: + // For each active dictionary, stores local info. + // Indexed by server hash. + struct DictionaryInfo { + base::Time last_used; + int use_count; + size_t size; + + DictionaryInfo() : use_count(0), size(0) {} + DictionaryInfo(const base::Time& last_used, size_t size) + : last_used(last_used), use_count(0), size(size) {} + DictionaryInfo(const DictionaryInfo& rhs) = default; + DictionaryInfo& operator=(const DictionaryInfo& rhs) = default; + }; + + void OnMemoryPressure( + base::MemoryPressureListener::MemoryPressureLevel level); + + net::SdchManager* manager_; + net::SdchDictionaryFetcher fetcher_; + + std::map<std::string, DictionaryInfo> local_dictionary_info_; + size_t total_dictionary_bytes_; + + scoped_ptr<base::Clock> clock_; + + size_t max_total_dictionary_size_; + size_t min_space_for_dictionary_fetch_; + + base::MemoryPressureListener memory_pressure_listener_; + + DISALLOW_COPY_AND_ASSIGN(SdchOwner); +}; + +} // namespace net + +#endif // NET_SDCH_SDCH_OWNER_H_ diff --git a/net/sdch/sdch_owner_unittest.cc b/net/sdch/sdch_owner_unittest.cc new file mode 100644 index 0000000..e89533a --- /dev/null +++ b/net/sdch/sdch_owner_unittest.cc @@ -0,0 +1,491 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/memory/memory_pressure_listener.h" +#include "base/run_loop.h" +#include "base/strings/stringprintf.h" +#include "base/test/simple_test_clock.h" +#include "net/base/net_log.h" +#include "net/base/sdch_manager.h" +#include "net/sdch/sdch_owner.h" +#include "net/url_request/url_request.h" +#include "net/url_request/url_request_context.h" +#include "net/url_request/url_request_error_job.h" +#include "net/url_request/url_request_job.h" +#include "net/url_request/url_request_job_factory.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { + +static const char generic_url[] = "http://www.example.com"; +static const char generic_domain[] = "www.example.com"; + +static std::string NewSdchDictionary(size_t dictionary_size) { + std::string dictionary; + dictionary.append("Domain: "); + dictionary.append(generic_domain); + dictionary.append("\n"); + dictionary.append("\n"); + + size_t original_dictionary_size = dictionary.size(); + dictionary.resize(dictionary_size); + for (size_t i = original_dictionary_size; i < dictionary_size; ++i) + dictionary[i] = static_cast<char>((i % 127) + 1); + + return dictionary; +} + +int outstanding_url_request_error_counting_jobs = 0; +base::Closure* empty_url_request_jobs_callback = 0; + +// Variation of URLRequestErrorJob to count number of outstanding +// instances and notify when that goes to zero. +class URLRequestErrorCountingJob : public URLRequestJob { + public: + URLRequestErrorCountingJob(URLRequest* request, + NetworkDelegate* network_delegate, + int error) + : URLRequestJob(request, network_delegate), + error_(error), + weak_factory_(this) { + ++outstanding_url_request_error_counting_jobs; + } + + void Start() override { + base::MessageLoop::current()->PostTask( + FROM_HERE, base::Bind(&URLRequestErrorCountingJob::StartAsync, + weak_factory_.GetWeakPtr())); + } + + private: + ~URLRequestErrorCountingJob() override { + --outstanding_url_request_error_counting_jobs; + if (0 == outstanding_url_request_error_counting_jobs && + empty_url_request_jobs_callback) { + empty_url_request_jobs_callback->Run(); + } + } + + void StartAsync() { + NotifyStartError(URLRequestStatus(URLRequestStatus::FAILED, error_)); + } + + int error_; + + base::WeakPtrFactory<URLRequestErrorCountingJob> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(URLRequestErrorCountingJob); +}; + +static int error_jobs_created = 0; + +class MockURLRequestJobFactory : public URLRequestJobFactory { + public: + MockURLRequestJobFactory() {} + + ~MockURLRequestJobFactory() override {} + + URLRequestJob* MaybeCreateJobWithProtocolHandler( + const std::string& scheme, + URLRequest* request, + NetworkDelegate* network_delegate) const override { + ++error_jobs_created; + return new URLRequestErrorCountingJob(request, network_delegate, + ERR_INTERNET_DISCONNECTED); + } + + URLRequestJob* MaybeInterceptRedirect(URLRequest* request, + NetworkDelegate* network_delegate, + const GURL& location) const override { + return nullptr; + } + + URLRequestJob* MaybeInterceptResponse( + URLRequest* request, + NetworkDelegate* network_delegate) const override { + return nullptr; + } + + bool IsHandledProtocol(const std::string& scheme) const override { + return scheme == "http"; + }; + + bool IsHandledURL(const GURL& url) const override { + return url.SchemeIs("http"); + } + + bool IsSafeRedirectTarget(const GURL& location) const override { + return false; + } +}; + +class SdchOwnerTest : public testing::Test { + public: + static const size_t kMaxSizeForTesting = 1000 * 50; + static const size_t kMinFetchSpaceForTesting = 500; + + SdchOwnerTest() + : last_jobs_created_(error_jobs_created), + dictionary_creation_index_(0), + sdch_owner_(&sdch_manager_, &url_request_context_) { + // Any jobs created on this context will immediately error, + // which leaves the test in control of signals to SdchOwner. + url_request_context_.set_job_factory(&job_factory_); + + // Reduce sizes to reduce time for string operations. + sdch_owner_.SetMaxTotalDictionarySize(kMaxSizeForTesting); + sdch_owner_.SetMinSpaceForDictionaryFetch(kMinFetchSpaceForTesting); + } + + SdchManager& sdch_manager() { return sdch_manager_; } + SdchOwner& sdch_owner() { return sdch_owner_; } + BoundNetLog& bound_net_log() { return net_log_; } + + int JobsRecentlyCreated() { + int result = error_jobs_created - last_jobs_created_; + last_jobs_created_ = error_jobs_created; + return result; + } + + bool DictionaryPresentInManager(const std::string& server_hash) { + // Presumes all tests use generic url. + SdchProblemCode tmp; + scoped_ptr<SdchManager::DictionarySet> set( + sdch_manager_.GetDictionarySetByHash(GURL(generic_url), server_hash, + &tmp)); + return !!set.get(); + } + + void SignalGetDictionaryAndClearJobs(GURL request_url, GURL dictionary_url) { + sdch_owner().OnGetDictionary(&sdch_manager_, request_url, dictionary_url); + if (outstanding_url_request_error_counting_jobs == 0) + return; + base::RunLoop run_loop; + base::Closure quit_closure(run_loop.QuitClosure()); + empty_url_request_jobs_callback = &quit_closure; + run_loop.Run(); + empty_url_request_jobs_callback = NULL; + } + + // Create a unique (by hash) dictionary of the given size, + // associate it with a unique URL, add it to the manager through + // SdchOwner::OnDictionaryFetched(), and return whether that + // addition was successful or not. + bool CreateAndAddDictionary(size_t size, std::string* server_hash_p) { + GURL dictionary_url( + base::StringPrintf("%s/d%d", generic_url, dictionary_creation_index_)); + std::string dictionary_text(NewSdchDictionary(size - 4)); + dictionary_text += base::StringPrintf("%04d", dictionary_creation_index_); + ++dictionary_creation_index_; + std::string client_hash; + std::string server_hash; + SdchManager::GenerateHash(dictionary_text, &client_hash, &server_hash); + + if (DictionaryPresentInManager(server_hash)) + return false; + sdch_owner().OnDictionaryFetched(dictionary_text, dictionary_url, net_log_); + if (server_hash_p) + *server_hash_p = server_hash; + return DictionaryPresentInManager(server_hash); + } + + private: + int last_jobs_created_; + BoundNetLog net_log_; + int dictionary_creation_index_; + + // The dependencies of these objects (sdch_owner_ -> {sdch_manager_, + // url_request_context_}, url_request_context_->job_factory_) require + // this order for correct destruction semantics. + MockURLRequestJobFactory job_factory_; + URLRequestContext url_request_context_; + SdchManager sdch_manager_; + SdchOwner sdch_owner_; + + DISALLOW_COPY_AND_ASSIGN(SdchOwnerTest); +}; + +// Does OnGetDictionary result in a fetch when there's enough space, and not +// when there's not? +TEST_F(SdchOwnerTest, OnGetDictionary_Fetching) { + GURL request_url(std::string(generic_url) + "/r1"); + + // Fetch generated when empty. + GURL dict_url1(std::string(generic_url) + "/d1"); + EXPECT_EQ(0, JobsRecentlyCreated()); + SignalGetDictionaryAndClearJobs(request_url, dict_url1); + EXPECT_EQ(1, JobsRecentlyCreated()); + + // Fetch generated when half full. + GURL dict_url2(std::string(generic_url) + "/d2"); + std::string dictionary1(NewSdchDictionary(kMaxSizeForTesting / 2)); + sdch_owner().OnDictionaryFetched(dictionary1, dict_url1, bound_net_log()); + EXPECT_EQ(0, JobsRecentlyCreated()); + SignalGetDictionaryAndClearJobs(request_url, dict_url2); + EXPECT_EQ(1, JobsRecentlyCreated()); + + // Fetch not generated when close to completely full. + GURL dict_url3(std::string(generic_url) + "/d3"); + std::string dictionary2(NewSdchDictionary( + (kMaxSizeForTesting / 2 - kMinFetchSpaceForTesting / 2))); + sdch_owner().OnDictionaryFetched(dictionary2, dict_url2, bound_net_log()); + EXPECT_EQ(0, JobsRecentlyCreated()); + SignalGetDictionaryAndClearJobs(request_url, dict_url3); + EXPECT_EQ(0, JobsRecentlyCreated()); +} + +// Make sure attempts to add dictionaries do what they should. +TEST_F(SdchOwnerTest, OnDictionaryFetched_Fetching) { + GURL request_url(std::string(generic_url) + "/r1"); + std::string client_hash; + std::string server_hash; + + // Add successful when empty. + EXPECT_TRUE(CreateAndAddDictionary(kMaxSizeForTesting / 2, nullptr)); + EXPECT_EQ(0, JobsRecentlyCreated()); + + // Add successful when half full. + EXPECT_TRUE(CreateAndAddDictionary(kMaxSizeForTesting / 2, nullptr)); + EXPECT_EQ(0, JobsRecentlyCreated()); + + // Add unsuccessful when full. + EXPECT_FALSE(CreateAndAddDictionary(kMaxSizeForTesting / 2, nullptr)); + EXPECT_EQ(0, JobsRecentlyCreated()); +} + +// Confirm auto-eviction happens if space is needed. +TEST_F(SdchOwnerTest, ConfirmAutoEviction) { + std::string server_hash_d1; + std::string server_hash_d2; + std::string server_hash_d3; + + // Add two dictionaries, one recent, one more than a day in the past. + EXPECT_TRUE(CreateAndAddDictionary(kMaxSizeForTesting / 2, &server_hash_d1)); + + scoped_ptr<base::SimpleTestClock> clock(new base::SimpleTestClock); + clock->SetNow(base::Time::Now() - base::TimeDelta::FromDays(2)); + sdch_owner().SetClockForTesting(clock.Pass()); + + EXPECT_TRUE(CreateAndAddDictionary(kMaxSizeForTesting / 2, &server_hash_d2)); + + EXPECT_TRUE(DictionaryPresentInManager(server_hash_d1)); + EXPECT_TRUE(DictionaryPresentInManager(server_hash_d2)); + + // The addition of a new dictionary should succeed, evicting the old one. + clock.reset(new base::SimpleTestClock); + clock->SetNow(base::Time::Now()); + sdch_owner().SetClockForTesting(clock.Pass()); + + EXPECT_TRUE(CreateAndAddDictionary(kMaxSizeForTesting / 2, &server_hash_d3)); + EXPECT_TRUE(DictionaryPresentInManager(server_hash_d1)); + EXPECT_FALSE(DictionaryPresentInManager(server_hash_d2)); + EXPECT_TRUE(DictionaryPresentInManager(server_hash_d3)); +} + +// Confirm auto-eviction happens if space is needed, with a more complicated +// situation +TEST_F(SdchOwnerTest, ConfirmAutoEviction_2) { + std::string server_hash_d1; + std::string server_hash_d2; + std::string server_hash_d3; + + // Add dictionaries, one recent, two more than a day in the past that + // between them add up to the space needed. + EXPECT_TRUE(CreateAndAddDictionary(kMaxSizeForTesting / 2, &server_hash_d1)); + + scoped_ptr<base::SimpleTestClock> clock(new base::SimpleTestClock); + clock->SetNow(base::Time::Now() - base::TimeDelta::FromDays(2)); + sdch_owner().SetClockForTesting(clock.Pass()); + + EXPECT_TRUE(CreateAndAddDictionary(kMaxSizeForTesting / 4, &server_hash_d2)); + EXPECT_TRUE(CreateAndAddDictionary(kMaxSizeForTesting / 4, &server_hash_d3)); + + EXPECT_TRUE(DictionaryPresentInManager(server_hash_d1)); + EXPECT_TRUE(DictionaryPresentInManager(server_hash_d2)); + EXPECT_TRUE(DictionaryPresentInManager(server_hash_d3)); + + // The addition of a new dictionary should succeed, evicting the old one. + clock.reset(new base::SimpleTestClock); + clock->SetNow(base::Time::Now()); + sdch_owner().SetClockForTesting(clock.Pass()); + + std::string server_hash_d4; + EXPECT_TRUE(CreateAndAddDictionary(kMaxSizeForTesting / 2, &server_hash_d4)); + EXPECT_TRUE(DictionaryPresentInManager(server_hash_d1)); + EXPECT_FALSE(DictionaryPresentInManager(server_hash_d2)); + EXPECT_FALSE(DictionaryPresentInManager(server_hash_d3)); + EXPECT_TRUE(DictionaryPresentInManager(server_hash_d4)); +} + +// Confirm if only one dictionary needs to be evicted it's the oldest. +TEST_F(SdchOwnerTest, ConfirmAutoEviction_Oldest) { + std::string server_hash_d1; + std::string server_hash_d2; + std::string server_hash_d3; + + // Add dictionaries, one recent, one two days in the past, and one + // four days in the past. + EXPECT_TRUE(CreateAndAddDictionary(kMaxSizeForTesting / 4, &server_hash_d1)); + + scoped_ptr<base::SimpleTestClock> clock(new base::SimpleTestClock); + clock->SetNow(base::Time::Now() - base::TimeDelta::FromDays(2)); + sdch_owner().SetClockForTesting(clock.Pass()); + EXPECT_TRUE(CreateAndAddDictionary(kMaxSizeForTesting / 4, &server_hash_d2)); + + clock.reset(new base::SimpleTestClock); + clock->SetNow(base::Time::Now() - base::TimeDelta::FromDays(4)); + sdch_owner().SetClockForTesting(clock.Pass()); + EXPECT_TRUE(CreateAndAddDictionary(kMaxSizeForTesting / 4, &server_hash_d3)); + + EXPECT_TRUE(DictionaryPresentInManager(server_hash_d1)); + EXPECT_TRUE(DictionaryPresentInManager(server_hash_d2)); + EXPECT_TRUE(DictionaryPresentInManager(server_hash_d3)); + + // The addition of a new dictionary should succeed, evicting only the + // oldest one. + clock.reset(new base::SimpleTestClock); + clock->SetNow(base::Time::Now()); + sdch_owner().SetClockForTesting(clock.Pass()); + + std::string server_hash_d4; + EXPECT_TRUE(CreateAndAddDictionary(kMaxSizeForTesting / 2, &server_hash_d4)); + EXPECT_TRUE(DictionaryPresentInManager(server_hash_d1)); + EXPECT_TRUE(DictionaryPresentInManager(server_hash_d2)); + EXPECT_FALSE(DictionaryPresentInManager(server_hash_d3)); + EXPECT_TRUE(DictionaryPresentInManager(server_hash_d4)); +} + +// Confirm using a dictionary changes eviction behavior properly. +TEST_F(SdchOwnerTest, UseChangesEviction) { + std::string server_hash_d1; + std::string server_hash_d2; + std::string server_hash_d3; + + // Add dictionaries, one recent, one two days in the past, and one + // four days in the past. + EXPECT_TRUE(CreateAndAddDictionary(kMaxSizeForTesting / 4, &server_hash_d1)); + + scoped_ptr<base::SimpleTestClock> clock(new base::SimpleTestClock); + clock->SetNow(base::Time::Now() - base::TimeDelta::FromDays(2)); + sdch_owner().SetClockForTesting(clock.Pass()); + EXPECT_TRUE(CreateAndAddDictionary(kMaxSizeForTesting / 4, &server_hash_d2)); + + clock.reset(new base::SimpleTestClock); + clock->SetNow(base::Time::Now() - base::TimeDelta::FromDays(4)); + sdch_owner().SetClockForTesting(clock.Pass()); + EXPECT_TRUE(CreateAndAddDictionary(kMaxSizeForTesting / 4, &server_hash_d3)); + + EXPECT_TRUE(DictionaryPresentInManager(server_hash_d1)); + EXPECT_TRUE(DictionaryPresentInManager(server_hash_d2)); + EXPECT_TRUE(DictionaryPresentInManager(server_hash_d3)); + + clock.reset(new base::SimpleTestClock); + clock->SetNow(base::Time::Now()); + sdch_owner().SetClockForTesting(clock.Pass()); + + // Use the oldest dictionary. + sdch_owner().OnDictionaryUsed(&sdch_manager(), server_hash_d3); + + // The addition of a new dictionary should succeed, evicting only the + // newer stale one. + std::string server_hash_d4; + EXPECT_TRUE(CreateAndAddDictionary(kMaxSizeForTesting / 2, &server_hash_d4)); + EXPECT_TRUE(DictionaryPresentInManager(server_hash_d1)); + EXPECT_FALSE(DictionaryPresentInManager(server_hash_d2)); + EXPECT_TRUE(DictionaryPresentInManager(server_hash_d3)); + EXPECT_TRUE(DictionaryPresentInManager(server_hash_d4)); +} + +// Confirm using a dictionary can prevent the addition of a new dictionary. +TEST_F(SdchOwnerTest, UsePreventsAddition) { + std::string server_hash_d1; + std::string server_hash_d2; + std::string server_hash_d3; + + // Add dictionaries, one recent, one two days in the past, and one + // four days in the past. + EXPECT_TRUE(CreateAndAddDictionary(kMaxSizeForTesting / 4, &server_hash_d1)); + + scoped_ptr<base::SimpleTestClock> clock(new base::SimpleTestClock); + clock->SetNow(base::Time::Now() - base::TimeDelta::FromDays(2)); + sdch_owner().SetClockForTesting(clock.Pass()); + EXPECT_TRUE(CreateAndAddDictionary(kMaxSizeForTesting / 4, &server_hash_d2)); + + clock.reset(new base::SimpleTestClock); + clock->SetNow(base::Time::Now() - base::TimeDelta::FromDays(4)); + sdch_owner().SetClockForTesting(clock.Pass()); + EXPECT_TRUE(CreateAndAddDictionary(kMaxSizeForTesting / 4, &server_hash_d3)); + + EXPECT_TRUE(DictionaryPresentInManager(server_hash_d1)); + EXPECT_TRUE(DictionaryPresentInManager(server_hash_d2)); + EXPECT_TRUE(DictionaryPresentInManager(server_hash_d3)); + + clock.reset(new base::SimpleTestClock); + clock->SetNow(base::Time::Now()); + sdch_owner().SetClockForTesting(clock.Pass()); + + // Use the older dictionaries. + sdch_owner().OnDictionaryUsed(&sdch_manager(), server_hash_d2); + sdch_owner().OnDictionaryUsed(&sdch_manager(), server_hash_d3); + + // The addition of a new dictionary should fail, not evicting anything. + std::string server_hash_d4; + EXPECT_FALSE(CreateAndAddDictionary(kMaxSizeForTesting / 2, &server_hash_d4)); + EXPECT_TRUE(DictionaryPresentInManager(server_hash_d1)); + EXPECT_TRUE(DictionaryPresentInManager(server_hash_d2)); + EXPECT_TRUE(DictionaryPresentInManager(server_hash_d3)); + EXPECT_FALSE(DictionaryPresentInManager(server_hash_d4)); +} + +// Confirm clear gets all the space back. +TEST_F(SdchOwnerTest, ClearReturnsSpace) { + std::string server_hash_d1; + std::string server_hash_d2; + + // Take up all the space. + EXPECT_TRUE(CreateAndAddDictionary(kMaxSizeForTesting, &server_hash_d1)); + + // Addition should fail. + EXPECT_FALSE(CreateAndAddDictionary(kMaxSizeForTesting, &server_hash_d2)); + + EXPECT_TRUE(DictionaryPresentInManager(server_hash_d1)); + EXPECT_FALSE(DictionaryPresentInManager(server_hash_d2)); + + sdch_manager().ClearData(); + EXPECT_FALSE(DictionaryPresentInManager(server_hash_d1)); + EXPECT_FALSE(DictionaryPresentInManager(server_hash_d2)); + + // Addition should now succeed. + EXPECT_TRUE(CreateAndAddDictionary(kMaxSizeForTesting, nullptr)); +} + +// Confirm memory pressure gets all the space back. +TEST_F(SdchOwnerTest, MemoryPressureReturnsSpace) { + std::string server_hash_d1; + std::string server_hash_d2; + + // Take up all the space. + EXPECT_TRUE(CreateAndAddDictionary(kMaxSizeForTesting, &server_hash_d1)); + + // Addition should fail. + EXPECT_FALSE(CreateAndAddDictionary(kMaxSizeForTesting, &server_hash_d2)); + + EXPECT_TRUE(DictionaryPresentInManager(server_hash_d1)); + EXPECT_FALSE(DictionaryPresentInManager(server_hash_d2)); + + base::MemoryPressureListener::NotifyMemoryPressure( + base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE); + // The notification may have (implementation note: does :-}) use a PostTask, + // so we drain the local message queue. This should be safe (i.e. not have + // an inifinite number of messages) in a unit test. + base::RunLoop().RunUntilIdle(); + + EXPECT_FALSE(DictionaryPresentInManager(server_hash_d1)); + EXPECT_FALSE(DictionaryPresentInManager(server_hash_d2)); + + // Addition should now succeed. + EXPECT_TRUE(CreateAndAddDictionary(kMaxSizeForTesting, nullptr)); +} + +} // namespace net |