diff --git a/cobalt/loader/BUILD.gn b/cobalt/loader/BUILD.gn index 9b808f9d95e4..c2ee44e629b0 100644 --- a/cobalt/loader/BUILD.gn +++ b/cobalt/loader/BUILD.gn @@ -178,6 +178,7 @@ target(gtest_target_type, "loader_test") { sources = [ "blob_fetcher_test.cc", + "fetcher_cache_test.cc", "fetcher_factory_test.cc", "fetcher_test.h", "file_fetcher_test.cc", @@ -204,6 +205,7 @@ target(gtest_target_type, "loader_test") { "//testing/gmock", "//testing/gtest", "//third_party/ots:ots", + "//url", ] data_deps = [ ":copy_loader_test_data" ] diff --git a/cobalt/loader/fetcher_cache.h b/cobalt/loader/fetcher_cache.h index 8f6e2b0ea90e..de32a1f57771 100644 --- a/cobalt/loader/fetcher_cache.h +++ b/cobalt/loader/fetcher_cache.h @@ -42,6 +42,16 @@ class FetcherCache : public base::RefCountedThreadSafe { Loader::FetcherCreator GetFetcherCreator( const GURL& url, const Loader::FetcherCreator& real_fetcher_creator); void NotifyResourceRequested(const std::string& url); + size_t size() const { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + CHECK_EQ(thread_id_, SbThreadGetId()); + return total_size_; + } + size_t capacity() const { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + CHECK_EQ(thread_id_, SbThreadGetId()); + return capacity_; + } // To signal the imminent destruction of this object. If everything is // working as expected, there shouldn't be any other reference of this object, diff --git a/cobalt/loader/fetcher_cache_test.cc b/cobalt/loader/fetcher_cache_test.cc new file mode 100644 index 000000000000..217ad1a43b59 --- /dev/null +++ b/cobalt/loader/fetcher_cache_test.cc @@ -0,0 +1,421 @@ +// Copyright 2024 The Cobalt Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "cobalt/loader/fetcher_cache.h" + +#include +#include +#include + +#include "base/bind.h" +#include "base/memory/ref_counted.h" +#include "base/run_loop.h" +#include "base/strings/string_number_conversions.h" +#include "base/task/post_task.h" +#include "base/task/task_scheduler/task_scheduler.h" +#include "base/test/scoped_task_environment.h" +#include "base/threading/thread_task_runner_handle.h" +#include "base/time/time.h" +#include "cobalt/loader/fetcher.h" +#include "net/http/http_response_headers.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "url/gurl.h" + +namespace cobalt { +namespace loader { +namespace { + +constexpr int kFetcherCapacity = 1024 * 1024; + +class StubFetcher : public Fetcher { + public: + explicit StubFetcher(Handler* handler) : Fetcher(handler) {} + ~StubFetcher() override {} + + void FireResponseStarted( + const scoped_refptr& headers) { + auto response_type = handler()->OnResponseStarted(this, headers); + if (response_type == kLoadResponseAbort) { + return; + } + } + + void FireReceived(const char* data, size_t size) { + handler()->OnReceived(this, data, size); + } + + void FireDone() { handler()->OnDone(this); } + + void FireFetcherProcess( + const scoped_refptr& headers, const char* data, + size_t size) { + LoadResponseType response = handler()->OnResponseStarted(this, headers); + if (response == kLoadResponseContinue) { + handler()->OnReceived(this, data, size); + handler()->OnDone(this); + } + } +}; + +class StubFetcherHandler : public Fetcher::Handler { + public: + // From Fetcher::Handler. + void OnReceived(Fetcher* fetcher, const char* data, size_t size) override {} + void OnDone(Fetcher* fetcher) override {} + void OnError(Fetcher* fetcher, const std::string& error_message) override {} +}; + +struct StubFetcherFactory { + public: + StubFetcherFactory() {} + // Way to access the last fetcher created by the fetcher factory. + StubFetcher* fetcher; + + std::unique_ptr Create(Fetcher::Handler* handler) { + fetcher = new StubFetcher(handler); + return base::WrapUnique(fetcher); + } + + Loader::FetcherCreator GetFetcherCreator() { + return base::Bind(&StubFetcherFactory::Create, base::Unretained(this)); + } +}; + +std::unique_ptr CreateFetcher(Fetcher::Handler* handler) { + StubFetcher* fetcher = new StubFetcher(handler); + return base::WrapUnique(fetcher); +} + +} // namespace + +class FetcherCacheTest : public ::testing::Test { + protected: + class OngoingFetcherFactory { + public: + OngoingFetcherFactory(const char* name, size_t capacity) { + fetcher_cache_ = base::MakeRefCounted(name, capacity); + } + ~OngoingFetcherFactory() { + if (fetcher_cache_) { + fetcher_cache_->DestroySoon(); + } + } + + size_t size() const { return fetcher_cache_->size(); } + + size_t capacity() const { return fetcher_cache_->capacity(); } + + Loader::FetcherCreator MakeCachedFetcherCreator( + const GURL& url, const Loader::FetcherCreator& real_fetcher_creator) { + return fetcher_cache_->GetFetcherCreator(url, real_fetcher_creator); + } + + private: + scoped_refptr fetcher_cache_; + }; + + const std::string data_ = "Test string! Test string! Test!!"; + base::test::ScopedTaskEnvironment scoped_task_environment_; + OngoingFetcherFactory fetcher_factory_{"cache_name", kFetcherCapacity}; +}; + +TEST_F(FetcherCacheTest, CtorAndDtor) { + EXPECT_EQ(kFetcherCapacity, fetcher_factory_.capacity()); +} + +TEST_F(FetcherCacheTest, MakeFetcher) { + auto fetcher_creator = fetcher_factory_.MakeCachedFetcherCreator( + GURL("https://example.com"), base::Bind(&CreateFetcher)); + EXPECT_EQ(0, fetcher_factory_.size()); +} + +TEST_F(FetcherCacheTest, SunnyDay) { + StubFetcherHandler handler; + StubFetcherFactory stub_fetcher_factory; + auto cb = stub_fetcher_factory.GetFetcherCreator(); + + auto fetcher_creator = fetcher_factory_.MakeCachedFetcherCreator( + GURL("https://example.com"), cb); + auto fetcher = fetcher_creator.Run(&handler); + + scoped_refptr headers = + base::MakeRefCounted("HTTP/1.1 200 OK\n"); + + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::BindOnce(&StubFetcher::FireResponseStarted, + base::Unretained(stub_fetcher_factory.fetcher), headers)); + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::BindOnce(&StubFetcher::FireReceived, + base::Unretained(stub_fetcher_factory.fetcher), + data_.c_str(), data_.size())); + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::BindOnce(&StubFetcher::FireDone, + base::Unretained(stub_fetcher_factory.fetcher))); + + scoped_task_environment_.RunUntilIdle(); + EXPECT_EQ(data_.capacity(), fetcher_factory_.size()); +} + +TEST_F(FetcherCacheTest, DuplicateFetcherCache) { + StubFetcherHandler handler; + scoped_refptr headers = + base::MakeRefCounted("HTTP/1.1 200 OK\n"); + + std::vector stub_fetcher_factorys; + std::vector fetcher_creators; + std::vector> fetchers; + + for (size_t i = 0; i < 2; i++) { + StubFetcherFactory stub_fetcher_factory; + Loader::FetcherCreator fetcher_creator = + fetcher_factory_.MakeCachedFetcherCreator( + GURL("https://example.com"), + stub_fetcher_factory.GetFetcherCreator()); + std::unique_ptr fetcher = fetcher_creator.Run(&handler); + + stub_fetcher_factorys.push_back(stub_fetcher_factory); + fetcher_creators.push_back(fetcher_creator); + fetchers.push_back(std::move(fetcher)); + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::BindOnce(&StubFetcher::FireFetcherProcess, + base::Unretained(stub_fetcher_factorys[i].fetcher), + headers, data_.c_str(), data_.size())); + } + + scoped_task_environment_.RunUntilIdle(); + EXPECT_EQ(data_.capacity(), fetcher_factory_.size()); +} + +TEST_F(FetcherCacheTest, MultipleFetcherCache) { + StubFetcherHandler handler; + scoped_refptr headers = + base::MakeRefCounted("HTTP/1.1 200 OK\n"); + const size_t kMaxNumCache = 10; + + std::vector stub_fetcher_factorys; + std::vector fetcher_creators; + std::vector> fetchers; + + for (size_t i = 0; i < kMaxNumCache; i++) { + StubFetcherFactory stub_fetcher_factory; + Loader::FetcherCreator fetcher_creator = + fetcher_factory_.MakeCachedFetcherCreator( + GURL("https://example.com" + base::NumberToString(i)), + stub_fetcher_factory.GetFetcherCreator()); + std::unique_ptr fetcher = fetcher_creator.Run(&handler); + + stub_fetcher_factorys.push_back(stub_fetcher_factory); + fetcher_creators.push_back(fetcher_creator); + fetchers.push_back(std::move(fetcher)); + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::BindOnce(&StubFetcher::FireFetcherProcess, + base::Unretained(stub_fetcher_factorys[i].fetcher), + headers, data_.c_str(), data_.size())); + } + + scoped_task_environment_.RunUntilIdle(); + EXPECT_EQ(data_.capacity() * kMaxNumCache, fetcher_factory_.size()); +} + +TEST_F(FetcherCacheTest, MaxFetcherCache) { + const size_t capacity = kFetcherCapacity; + const size_t data_capacity = data_.capacity(); + StubFetcherHandler handler; + scoped_refptr headers = + base::MakeRefCounted("HTTP/1.1 200 OK\n"); + // kMaxNumCache = ceil(capacity / data_.capacity()) + 1, this will force + // to write a new cache and delete an old cache + const size_t kMaxNumCache = 1 + (capacity / data_capacity); + + std::vector stub_fetcher_factorys; + std::vector fetcher_creators; + std::vector> fetchers; + + for (size_t i = 0; i < kMaxNumCache; i++) { + StubFetcherFactory stub_fetcher_factory; + Loader::FetcherCreator fetcher_creator = + fetcher_factory_.MakeCachedFetcherCreator( + GURL("https://example.com" + base::NumberToString(i)), + stub_fetcher_factory.GetFetcherCreator()); + std::unique_ptr fetcher = fetcher_creator.Run(&handler); + + stub_fetcher_factorys.push_back(stub_fetcher_factory); + fetcher_creators.push_back(fetcher_creator); + fetchers.push_back(std::move(fetcher)); + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::BindOnce(&StubFetcher::FireFetcherProcess, + base::Unretained(stub_fetcher_factorys[i].fetcher), + headers, data_.c_str(), data_.size())); + } + + scoped_task_environment_.RunUntilIdle(); + EXPECT_EQ(data_capacity * (kMaxNumCache - 1), fetcher_factory_.size()); +} + +TEST_F(FetcherCacheTest, DisorderedSameFetcherCache) { + StubFetcherHandler handler; + scoped_refptr headers = + base::MakeRefCounted("HTTP/1.1 200 OK\n"); + + StubFetcherFactory stub_fetcher_factory1, stub_fetcher_factory2; + Loader::FetcherCreator fetcher_creator1 = + fetcher_factory_.MakeCachedFetcherCreator( + GURL("https://example.com"), + stub_fetcher_factory1.GetFetcherCreator()); + std::unique_ptr fetcher1 = fetcher_creator1.Run(&handler); + Loader::FetcherCreator fetcher_creator2 = + fetcher_factory_.MakeCachedFetcherCreator( + GURL("https://example.com"), + stub_fetcher_factory2.GetFetcherCreator()); + std::unique_ptr fetcher2 = fetcher_creator2.Run(&handler); + + // Task 1: start and receive + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::BindOnce(&StubFetcher::FireResponseStarted, + base::Unretained(stub_fetcher_factory1.fetcher), headers)); + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::BindOnce(&StubFetcher::FireReceived, + base::Unretained(stub_fetcher_factory1.fetcher), + data_.c_str(), data_.size())); + // Task 2: start and receive + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::BindOnce(&StubFetcher::FireResponseStarted, + base::Unretained(stub_fetcher_factory2.fetcher), headers)); + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::BindOnce(&StubFetcher::FireReceived, + base::Unretained(stub_fetcher_factory2.fetcher), + data_.c_str(), data_.size())); + // Task 1: done + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::BindOnce(&StubFetcher::FireDone, + base::Unretained(stub_fetcher_factory1.fetcher))); + // Task 2: done + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::BindOnce(&StubFetcher::FireDone, + base::Unretained(stub_fetcher_factory2.fetcher))); + + scoped_task_environment_.RunUntilIdle(); + EXPECT_EQ(data_.capacity(), fetcher_factory_.size()); +} + +TEST_F(FetcherCacheTest, DisorderedDiffFetcherCache) { + StubFetcherHandler handler; + scoped_refptr headers = + base::MakeRefCounted("HTTP/1.1 200 OK\n"); + + StubFetcherFactory stub_fetcher_factory1, stub_fetcher_factory2; + Loader::FetcherCreator fetcher_creator1 = + fetcher_factory_.MakeCachedFetcherCreator( + GURL("https://example1.com"), + stub_fetcher_factory1.GetFetcherCreator()); + std::unique_ptr fetcher1 = fetcher_creator1.Run(&handler); + Loader::FetcherCreator fetcher_creator2 = + fetcher_factory_.MakeCachedFetcherCreator( + GURL("https://example2.com"), + stub_fetcher_factory2.GetFetcherCreator()); + std::unique_ptr fetcher2 = fetcher_creator2.Run(&handler); + + // Task 1: start and receive + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::BindOnce(&StubFetcher::FireResponseStarted, + base::Unretained(stub_fetcher_factory1.fetcher), headers)); + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::BindOnce(&StubFetcher::FireReceived, + base::Unretained(stub_fetcher_factory1.fetcher), + data_.c_str(), data_.size())); + // Task 2: start and receive + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::BindOnce(&StubFetcher::FireResponseStarted, + base::Unretained(stub_fetcher_factory2.fetcher), headers)); + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::BindOnce(&StubFetcher::FireReceived, + base::Unretained(stub_fetcher_factory2.fetcher), + data_.c_str(), data_.size())); + // Task 1: done + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::BindOnce(&StubFetcher::FireDone, + base::Unretained(stub_fetcher_factory1.fetcher))); + // Task 2: done + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::BindOnce(&StubFetcher::FireDone, + base::Unretained(stub_fetcher_factory2.fetcher))); + + scoped_task_environment_.RunUntilIdle(); + EXPECT_EQ(data_.capacity() * 2, fetcher_factory_.size()); +} + +TEST_F(FetcherCacheTest, DisorderedSlowFetcherCache) { + StubFetcherHandler handler; + scoped_refptr headers = + base::MakeRefCounted("HTTP/1.1 200 OK\n"); + + StubFetcherFactory stub_fetcher_factory1, stub_fetcher_factory2; + Loader::FetcherCreator fetcher_creator1 = + fetcher_factory_.MakeCachedFetcherCreator( + GURL("https://example1.com"), + stub_fetcher_factory1.GetFetcherCreator()); + std::unique_ptr fetcher1 = fetcher_creator1.Run(&handler); + Loader::FetcherCreator fetcher_creator2 = + fetcher_factory_.MakeCachedFetcherCreator( + GURL("https://example2.com"), + stub_fetcher_factory2.GetFetcherCreator()); + std::unique_ptr fetcher2 = fetcher_creator2.Run(&handler); + + // Task 1: start and receive + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::BindOnce(&StubFetcher::FireResponseStarted, + base::Unretained(stub_fetcher_factory1.fetcher), headers)); + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::BindOnce(&StubFetcher::FireReceived, + base::Unretained(stub_fetcher_factory1.fetcher), + data_.c_str(), data_.size())); + // Task 2: start, receive, and done + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::BindOnce(&StubFetcher::FireResponseStarted, + base::Unretained(stub_fetcher_factory2.fetcher), headers)); + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::BindOnce(&StubFetcher::FireReceived, + base::Unretained(stub_fetcher_factory2.fetcher), + data_.c_str(), data_.size())); + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::BindOnce(&StubFetcher::FireDone, + base::Unretained(stub_fetcher_factory2.fetcher))); + // Task 1: done + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::BindOnce(&StubFetcher::FireDone, + base::Unretained(stub_fetcher_factory1.fetcher))); + + scoped_task_environment_.RunUntilIdle(); + EXPECT_EQ(data_.capacity() * 2, fetcher_factory_.size()); +} + +} // namespace loader +} // namespace cobalt