| // Copyright (c) 2012 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/http/http_cache.h" |
| |
| #include <algorithm> |
| #include <memory> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/bind.h" |
| #include "base/bind_helpers.h" |
| #include "base/format_macros.h" |
| #include "base/macros.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/run_loop.h" |
| #include "base/stl_util.h" |
| #include "base/strings/strcat.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "base/test/simple_test_clock.h" |
| #include "base/trace_event/memory_allocator_dump.h" |
| #include "base/trace_event/memory_dump_request_args.h" |
| #include "base/trace_event/process_memory_dump.h" |
| #include "base/trace_event/trace_event_argument.h" |
| #include "net/base/cache_type.h" |
| #include "net/base/elements_upload_data_stream.h" |
| #include "net/base/host_port_pair.h" |
| #include "net/base/ip_endpoint.h" |
| #include "net/base/load_flags.h" |
| #include "net/base/load_timing_info.h" |
| #include "net/base/load_timing_info_test_util.h" |
| #include "net/base/net_errors.h" |
| #include "net/base/upload_bytes_element_reader.h" |
| #include "net/cert/cert_status_flags.h" |
| #include "net/cert/x509_certificate.h" |
| #include "net/disk_cache/disk_cache.h" |
| #include "net/http/http_byte_range.h" |
| #include "net/http/http_cache_transaction.h" |
| #include "net/http/http_request_headers.h" |
| #include "net/http/http_request_info.h" |
| #include "net/http/http_response_headers.h" |
| #include "net/http/http_response_info.h" |
| #include "net/http/http_transaction.h" |
| #include "net/http/http_transaction_test_util.h" |
| #include "net/http/http_util.h" |
| #include "net/http/mock_http_cache.h" |
| #include "net/log/net_log_event_type.h" |
| #include "net/log/net_log_source.h" |
| #include "net/log/net_log_with_source.h" |
| #include "net/log/test_net_log.h" |
| #include "net/log/test_net_log_entry.h" |
| #include "net/log/test_net_log_util.h" |
| #include "net/socket/client_socket_handle.h" |
| #include "net/ssl/ssl_cert_request_info.h" |
| #include "net/ssl/ssl_connection_status_flags.h" |
| #include "net/test/cert_test_util.h" |
| #include "net/test/gtest_util.h" |
| #include "net/test/test_data_directory.h" |
| #include "net/test/test_with_scoped_task_environment.h" |
| #include "net/websockets/websocket_handshake_stream_base.h" |
| #include "starboard/common/string.h" |
| #include "starboard/memory.h" |
| #include "starboard/types.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| using net::test::IsError; |
| using net::test::IsOk; |
| using testing::Gt; |
| using testing::AllOf; |
| using testing::Contains; |
| using testing::Eq; |
| using testing::Field; |
| using testing::Contains; |
| using testing::ByRef; |
| |
| using base::Time; |
| |
| namespace net { |
| |
| using CacheEntryStatus = HttpResponseInfo::CacheEntryStatus; |
| |
| class WebSocketEndpointLockManager; |
| |
| namespace { |
| |
| // Returns a simple text serialization of the given |
| // |HttpResponseHeaders|. This is used by tests to verify that an |
| // |HttpResponseHeaders| matches an expectation string. |
| // |
| // * One line per header, written as: |
| // HEADER_NAME: HEADER_VALUE\n |
| // * The original case of header names is preserved. |
| // * Whitespace around head names/values is stripped. |
| // * Repeated headers are not aggregated. |
| // * Headers are listed in their original order. |
| // TODO(tfarina): this is a duplicate function from |
| // http_response_headers_unittest.cc:ToSimpleString(). Figure out how to merge |
| // them. crbug.com/488593 |
| std::string ToSimpleString(const scoped_refptr<HttpResponseHeaders>& parsed) { |
| std::string result = parsed->GetStatusLine() + "\n"; |
| |
| size_t iter = 0; |
| std::string name; |
| std::string value; |
| while (parsed->EnumerateHeaderLines(&iter, &name, &value)) { |
| std::string new_line = name + ": " + value + "\n"; |
| |
| result += new_line; |
| } |
| |
| return result; |
| } |
| |
| // Tests the load timing values of a request that goes through a |
| // MockNetworkTransaction. |
| void TestLoadTimingNetworkRequest(const LoadTimingInfo& load_timing_info) { |
| EXPECT_FALSE(load_timing_info.socket_reused); |
| EXPECT_NE(NetLogSource::kInvalidId, load_timing_info.socket_log_id); |
| |
| EXPECT_TRUE(load_timing_info.proxy_resolve_start.is_null()); |
| EXPECT_TRUE(load_timing_info.proxy_resolve_end.is_null()); |
| |
| ExpectConnectTimingHasTimes(load_timing_info.connect_timing, |
| CONNECT_TIMING_HAS_CONNECT_TIMES_ONLY); |
| EXPECT_LE(load_timing_info.connect_timing.connect_end, |
| load_timing_info.send_start); |
| |
| EXPECT_LE(load_timing_info.send_start, load_timing_info.send_end); |
| |
| // Set by URLRequest / URLRequestHttpJob, at a higher level. |
| EXPECT_TRUE(load_timing_info.request_start_time.is_null()); |
| EXPECT_TRUE(load_timing_info.request_start.is_null()); |
| EXPECT_TRUE(load_timing_info.receive_headers_end.is_null()); |
| } |
| |
| // Tests the load timing values of a request that receives a cached response. |
| void TestLoadTimingCachedResponse(const LoadTimingInfo& load_timing_info) { |
| EXPECT_FALSE(load_timing_info.socket_reused); |
| EXPECT_EQ(NetLogSource::kInvalidId, load_timing_info.socket_log_id); |
| |
| EXPECT_TRUE(load_timing_info.proxy_resolve_start.is_null()); |
| EXPECT_TRUE(load_timing_info.proxy_resolve_end.is_null()); |
| |
| ExpectConnectTimingHasNoTimes(load_timing_info.connect_timing); |
| |
| // Only the send start / end times should be sent, and they should have the |
| // same value. |
| EXPECT_FALSE(load_timing_info.send_start.is_null()); |
| EXPECT_EQ(load_timing_info.send_start, load_timing_info.send_end); |
| |
| // Set by URLRequest / URLRequestHttpJob, at a higher level. |
| EXPECT_TRUE(load_timing_info.request_start_time.is_null()); |
| EXPECT_TRUE(load_timing_info.request_start.is_null()); |
| EXPECT_TRUE(load_timing_info.receive_headers_end.is_null()); |
| } |
| |
| void DeferCallback(bool* defer) { |
| *defer = true; |
| } |
| |
| class DeleteCacheCompletionCallback : public TestCompletionCallbackBase { |
| public: |
| explicit DeleteCacheCompletionCallback(MockHttpCache* cache) |
| : cache_(cache) {} |
| |
| CompletionOnceCallback callback() { |
| return base::BindOnce(&DeleteCacheCompletionCallback::OnComplete, |
| base::Unretained(this)); |
| } |
| |
| private: |
| void OnComplete(int result) { |
| delete cache_; |
| SetResult(result); |
| } |
| |
| MockHttpCache* cache_; |
| |
| DISALLOW_COPY_AND_ASSIGN(DeleteCacheCompletionCallback); |
| }; |
| |
| //----------------------------------------------------------------------------- |
| // helpers |
| |
| void ReadAndVerifyTransaction(HttpTransaction* trans, |
| const MockTransaction& trans_info) { |
| std::string content; |
| int rv = ReadTransaction(trans, &content); |
| |
| EXPECT_THAT(rv, IsOk()); |
| std::string expected(trans_info.data); |
| EXPECT_EQ(expected, content); |
| } |
| |
| void ReadRemainingAndVerifyTransaction(HttpTransaction* trans, |
| const std::string& already_read, |
| const MockTransaction& trans_info) { |
| std::string content; |
| int rv = ReadTransaction(trans, &content); |
| EXPECT_THAT(rv, IsOk()); |
| |
| std::string expected(trans_info.data); |
| EXPECT_EQ(expected, already_read + content); |
| } |
| |
| void RunTransactionTestBase(HttpCache* cache, |
| const MockTransaction& trans_info, |
| const MockHttpRequest& request, |
| HttpResponseInfo* response_info, |
| const NetLogWithSource& net_log, |
| LoadTimingInfo* load_timing_info, |
| int64_t* sent_bytes, |
| int64_t* received_bytes, |
| IPEndPoint* remote_endpoint) { |
| TestCompletionCallback callback; |
| |
| // write to the cache |
| |
| std::unique_ptr<HttpTransaction> trans; |
| int rv = cache->CreateTransaction(DEFAULT_PRIORITY, &trans); |
| EXPECT_THAT(rv, IsOk()); |
| ASSERT_TRUE(trans.get()); |
| |
| rv = trans->Start(&request, callback.callback(), net_log); |
| if (rv == ERR_IO_PENDING) |
| rv = callback.WaitForResult(); |
| ASSERT_EQ(trans_info.start_return_code, rv); |
| |
| if (OK != rv) |
| return; |
| |
| const HttpResponseInfo* response = trans->GetResponseInfo(); |
| ASSERT_TRUE(response); |
| |
| if (response_info) |
| *response_info = *response; |
| |
| if (load_timing_info) { |
| // If a fake network connection is used, need a NetLog to get a fake socket |
| // ID. |
| EXPECT_TRUE(net_log.net_log()); |
| *load_timing_info = LoadTimingInfo(); |
| trans->GetLoadTimingInfo(load_timing_info); |
| } |
| |
| if (remote_endpoint) |
| ASSERT_TRUE(trans->GetRemoteEndpoint(remote_endpoint)); |
| |
| ReadAndVerifyTransaction(trans.get(), trans_info); |
| |
| if (sent_bytes) |
| *sent_bytes = trans->GetTotalSentBytes(); |
| if (received_bytes) |
| *received_bytes = trans->GetTotalReceivedBytes(); |
| } |
| |
| void RunTransactionTestWithRequest(HttpCache* cache, |
| const MockTransaction& trans_info, |
| const MockHttpRequest& request, |
| HttpResponseInfo* response_info) { |
| RunTransactionTestBase(cache, trans_info, request, response_info, |
| NetLogWithSource(), nullptr, nullptr, nullptr, |
| nullptr); |
| } |
| |
| void RunTransactionTestAndGetTiming(HttpCache* cache, |
| const MockTransaction& trans_info, |
| const NetLogWithSource& log, |
| LoadTimingInfo* load_timing_info) { |
| RunTransactionTestBase(cache, trans_info, MockHttpRequest(trans_info), |
| nullptr, log, load_timing_info, nullptr, nullptr, |
| nullptr); |
| } |
| |
| void RunTransactionTestAndGetTimingAndConnectedSocketAddress( |
| HttpCache* cache, |
| const MockTransaction& trans_info, |
| const NetLogWithSource& log, |
| LoadTimingInfo* load_timing_info, |
| IPEndPoint* remote_endpoint) { |
| RunTransactionTestBase(cache, trans_info, MockHttpRequest(trans_info), |
| nullptr, log, load_timing_info, nullptr, nullptr, |
| remote_endpoint); |
| } |
| |
| void RunTransactionTest(HttpCache* cache, const MockTransaction& trans_info) { |
| RunTransactionTestAndGetTiming(cache, trans_info, NetLogWithSource(), |
| nullptr); |
| } |
| |
| void RunTransactionTestWithLog(HttpCache* cache, |
| const MockTransaction& trans_info, |
| const NetLogWithSource& log) { |
| RunTransactionTestAndGetTiming(cache, trans_info, log, nullptr); |
| } |
| |
| void RunTransactionTestWithResponseInfo(HttpCache* cache, |
| const MockTransaction& trans_info, |
| HttpResponseInfo* response) { |
| RunTransactionTestWithRequest(cache, trans_info, MockHttpRequest(trans_info), |
| response); |
| } |
| |
| void RunTransactionTestWithResponseInfoAndGetTiming( |
| HttpCache* cache, |
| const MockTransaction& trans_info, |
| HttpResponseInfo* response, |
| const NetLogWithSource& log, |
| LoadTimingInfo* load_timing_info) { |
| RunTransactionTestBase(cache, trans_info, MockHttpRequest(trans_info), |
| response, log, load_timing_info, nullptr, nullptr, |
| nullptr); |
| } |
| |
| void RunTransactionTestWithResponse(HttpCache* cache, |
| const MockTransaction& trans_info, |
| std::string* response_headers) { |
| HttpResponseInfo response; |
| RunTransactionTestWithResponseInfo(cache, trans_info, &response); |
| *response_headers = ToSimpleString(response.headers); |
| } |
| |
| void RunTransactionTestWithResponseAndGetTiming( |
| HttpCache* cache, |
| const MockTransaction& trans_info, |
| std::string* response_headers, |
| const NetLogWithSource& log, |
| LoadTimingInfo* load_timing_info) { |
| HttpResponseInfo response; |
| RunTransactionTestBase(cache, trans_info, MockHttpRequest(trans_info), |
| &response, log, load_timing_info, nullptr, nullptr, |
| nullptr); |
| *response_headers = ToSimpleString(response.headers); |
| } |
| |
| // This class provides a handler for kFastNoStoreGET_Transaction so that the |
| // no-store header can be included on demand. |
| class FastTransactionServer { |
| public: |
| FastTransactionServer() { |
| no_store = false; |
| } |
| ~FastTransactionServer() = default; |
| |
| void set_no_store(bool value) { no_store = value; } |
| |
| static void FastNoStoreHandler(const HttpRequestInfo* request, |
| std::string* response_status, |
| std::string* response_headers, |
| std::string* response_data) { |
| if (no_store) |
| *response_headers = "Cache-Control: no-store\n"; |
| } |
| |
| private: |
| static bool no_store; |
| DISALLOW_COPY_AND_ASSIGN(FastTransactionServer); |
| }; |
| bool FastTransactionServer::no_store; |
| |
| const MockTransaction kFastNoStoreGET_Transaction = { |
| "http://www.google.com/nostore", |
| "GET", |
| base::Time(), |
| "", |
| LOAD_VALIDATE_CACHE, |
| "HTTP/1.1 200 OK", |
| "Cache-Control: max-age=10000\n", |
| base::Time(), |
| "<html><body>Google Blah Blah</body></html>", |
| TEST_MODE_SYNC_NET_START, |
| &FastTransactionServer::FastNoStoreHandler, |
| nullptr, |
| nullptr, |
| 0, |
| 0, |
| OK}; |
| |
| // This class provides a handler for kRangeGET_TransactionOK so that the range |
| // request can be served on demand. |
| class RangeTransactionServer { |
| public: |
| RangeTransactionServer() { |
| not_modified_ = false; |
| modified_ = false; |
| bad_200_ = false; |
| } |
| ~RangeTransactionServer() { |
| not_modified_ = false; |
| modified_ = false; |
| bad_200_ = false; |
| } |
| |
| // Returns only 416 or 304 when set. |
| void set_not_modified(bool value) { not_modified_ = value; } |
| |
| // Returns 206 when revalidating a range (instead of 304). |
| void set_modified(bool value) { modified_ = value; } |
| |
| // Returns 200 instead of 206 (a malformed response overall). |
| void set_bad_200(bool value) { bad_200_ = value; } |
| |
| // Other than regular range related behavior (and the flags mentioned above), |
| // the server reacts to requests headers like so: |
| // X-Require-Mock-Auth -> return 401. |
| // X-Require-Mock-Auth-Alt -> return 401. |
| // X-Return-Default-Range -> assume 40-49 was requested. |
| // The -Alt variant doesn't cause the MockNetworkTransaction to |
| // report that it IsReadyToRestartForAuth(). |
| static void RangeHandler(const HttpRequestInfo* request, |
| std::string* response_status, |
| std::string* response_headers, |
| std::string* response_data); |
| |
| private: |
| static bool not_modified_; |
| static bool modified_; |
| static bool bad_200_; |
| DISALLOW_COPY_AND_ASSIGN(RangeTransactionServer); |
| }; |
| bool RangeTransactionServer::not_modified_ = false; |
| bool RangeTransactionServer::modified_ = false; |
| bool RangeTransactionServer::bad_200_ = false; |
| |
| // A dummy extra header that must be preserved on a given request. |
| |
| // EXTRA_HEADER_LINE doesn't include a line terminator because it |
| // will be passed to AddHeaderFromString() which doesn't accept them. |
| #define EXTRA_HEADER_LINE "Extra: header" |
| |
| // EXTRA_HEADER contains a line terminator, as expected by |
| // AddHeadersFromString() (_not_ AddHeaderFromString()). |
| #define EXTRA_HEADER EXTRA_HEADER_LINE "\r\n" |
| |
| static const char kExtraHeaderKey[] = "Extra"; |
| |
| // Static. |
| void RangeTransactionServer::RangeHandler(const HttpRequestInfo* request, |
| std::string* response_status, |
| std::string* response_headers, |
| std::string* response_data) { |
| if (request->extra_headers.IsEmpty()) { |
| response_status->assign("HTTP/1.1 416 Requested Range Not Satisfiable"); |
| response_data->clear(); |
| return; |
| } |
| |
| // We want to make sure we don't delete extra headers. |
| EXPECT_TRUE(request->extra_headers.HasHeader(kExtraHeaderKey)); |
| |
| bool require_auth = |
| request->extra_headers.HasHeader("X-Require-Mock-Auth") || |
| request->extra_headers.HasHeader("X-Require-Mock-Auth-Alt"); |
| |
| if (require_auth && !request->extra_headers.HasHeader("Authorization")) { |
| response_status->assign("HTTP/1.1 401 Unauthorized"); |
| response_data->assign("WWW-Authenticate: Foo\n"); |
| return; |
| } |
| |
| if (not_modified_) { |
| response_status->assign("HTTP/1.1 304 Not Modified"); |
| response_data->clear(); |
| return; |
| } |
| |
| std::vector<HttpByteRange> ranges; |
| std::string range_header; |
| if (!request->extra_headers.GetHeader(HttpRequestHeaders::kRange, |
| &range_header) || |
| !HttpUtil::ParseRangeHeader(range_header, &ranges) || bad_200_ || |
| ranges.size() != 1) { |
| // This is not a byte range request. We return 200. |
| response_status->assign("HTTP/1.1 200 OK"); |
| response_headers->assign("Date: Wed, 28 Nov 2007 09:40:09 GMT"); |
| response_data->assign("Not a range"); |
| return; |
| } |
| |
| // We can handle this range request. |
| HttpByteRange byte_range = ranges[0]; |
| |
| if (request->extra_headers.HasHeader("X-Return-Default-Range")) { |
| byte_range.set_first_byte_position(40); |
| byte_range.set_last_byte_position(49); |
| } |
| |
| if (byte_range.first_byte_position() > 79) { |
| response_status->assign("HTTP/1.1 416 Requested Range Not Satisfiable"); |
| response_data->clear(); |
| return; |
| } |
| |
| EXPECT_TRUE(byte_range.ComputeBounds(80)); |
| int start = static_cast<int>(byte_range.first_byte_position()); |
| int end = static_cast<int>(byte_range.last_byte_position()); |
| |
| EXPECT_LT(end, 80); |
| |
| std::string content_range = base::StringPrintf( |
| "Content-Range: bytes %d-%d/80\n", start, end); |
| response_headers->append(content_range); |
| |
| if (!request->extra_headers.HasHeader("If-None-Match") || modified_) { |
| std::string data; |
| if (end == start) { |
| EXPECT_EQ(0, end % 10); |
| data = "r"; |
| } else { |
| EXPECT_EQ(9, (end - start) % 10); |
| for (int block_start = start; block_start < end; block_start += 10) { |
| base::StringAppendF(&data, "rg: %02d-%02d ", |
| block_start, block_start + 9); |
| } |
| } |
| *response_data = data; |
| |
| if (end - start != 9) { |
| // We also have to fix content-length. |
| int len = end - start + 1; |
| std::string content_length = base::StringPrintf("Content-Length: %d\n", |
| len); |
| response_headers->replace(response_headers->find("Content-Length:"), |
| content_length.size(), content_length); |
| } |
| } else { |
| response_status->assign("HTTP/1.1 304 Not Modified"); |
| response_data->clear(); |
| } |
| } |
| |
| const MockTransaction kRangeGET_TransactionOK = { |
| "http://www.google.com/range", "GET", base::Time(), |
| "Range: bytes = 40-49\r\n" EXTRA_HEADER, LOAD_NORMAL, |
| "HTTP/1.1 206 Partial Content", |
| "Last-Modified: Sat, 18 Apr 2007 01:10:43 GMT\n" |
| "ETag: \"foo\"\n" |
| "Accept-Ranges: bytes\n" |
| "Content-Length: 10\n", |
| base::Time(), "rg: 40-49 ", TEST_MODE_NORMAL, |
| &RangeTransactionServer::RangeHandler, nullptr, nullptr, 0, 0, OK}; |
| |
| const char kFullRangeData[] = |
| "rg: 00-09 rg: 10-19 rg: 20-29 rg: 30-39 " |
| "rg: 40-49 rg: 50-59 rg: 60-69 rg: 70-79 "; |
| |
| // Verifies the response headers (|response|) match a partial content |
| // response for the range starting at |start| and ending at |end|. |
| void Verify206Response(const std::string& response, int start, int end) { |
| std::string raw_headers( |
| HttpUtil::AssembleRawHeaders(response.data(), response.size())); |
| scoped_refptr<HttpResponseHeaders> headers( |
| new HttpResponseHeaders(raw_headers)); |
| |
| ASSERT_EQ(206, headers->response_code()); |
| |
| int64_t range_start, range_end, object_size; |
| ASSERT_TRUE( |
| headers->GetContentRangeFor206(&range_start, &range_end, &object_size)); |
| int64_t content_length = headers->GetContentLength(); |
| |
| int length = end - start + 1; |
| ASSERT_EQ(length, content_length); |
| ASSERT_EQ(start, range_start); |
| ASSERT_EQ(end, range_end); |
| } |
| |
| // Creates a truncated entry that can be resumed using byte ranges. |
| void CreateTruncatedEntry(std::string raw_headers, MockHttpCache* cache) { |
| // Create a disk cache entry that stores an incomplete resource. |
| disk_cache::Entry* entry; |
| ASSERT_TRUE(cache->CreateBackendEntry(kRangeGET_TransactionOK.url, &entry, |
| NULL)); |
| |
| raw_headers = |
| HttpUtil::AssembleRawHeaders(raw_headers.data(), raw_headers.size()); |
| |
| HttpResponseInfo response; |
| response.response_time = base::Time::Now(); |
| response.request_time = base::Time::Now(); |
| response.headers = new HttpResponseHeaders(raw_headers); |
| // Set the last argument for this to be an incomplete request. |
| EXPECT_TRUE(MockHttpCache::WriteResponseInfo(entry, &response, true, true)); |
| |
| scoped_refptr<IOBuffer> buf = base::MakeRefCounted<IOBuffer>(100); |
| int len = static_cast<int>(base::strlcpy(buf->data(), |
| "rg: 00-09 rg: 10-19 ", 100)); |
| TestCompletionCallback cb; |
| int rv = entry->WriteData(1, 0, buf.get(), len, cb.callback(), true); |
| EXPECT_EQ(len, cb.GetResult(rv)); |
| entry->Close(); |
| } |
| |
| // Verifies that there's an entry with this |key| with the truncated flag set to |
| // |flag_value|, and with an optional |data_size| (if not zero). |
| void VerifyTruncatedFlag(MockHttpCache* cache, |
| const std::string& key, |
| bool flag_value, |
| int data_size) { |
| disk_cache::Entry* entry; |
| ASSERT_TRUE(cache->OpenBackendEntry(key, &entry)); |
| disk_cache::ScopedEntryPtr closer(entry); |
| |
| HttpResponseInfo response; |
| bool truncated = !flag_value; |
| EXPECT_TRUE(MockHttpCache::ReadResponseInfo(entry, &response, &truncated)); |
| EXPECT_EQ(flag_value, truncated); |
| if (data_size) |
| EXPECT_EQ(data_size, entry->GetDataSize(1)); |
| } |
| |
| // Helper to represent a network HTTP response. |
| struct Response { |
| // Set this response into |trans|. |
| void AssignTo(MockTransaction* trans) const { |
| trans->status = status; |
| trans->response_headers = headers; |
| trans->data = body; |
| } |
| |
| std::string status_and_headers() const { |
| return std::string(status) + "\n" + std::string(headers); |
| } |
| |
| const char* status; |
| const char* headers; |
| const char* body; |
| }; |
| |
| struct Context { |
| Context() : result(ERR_IO_PENDING) {} |
| |
| int result; |
| TestCompletionCallback callback; |
| std::unique_ptr<HttpTransaction> trans; |
| }; |
| |
| class FakeWebSocketHandshakeStreamCreateHelper |
| : public WebSocketHandshakeStreamBase::CreateHelper { |
| public: |
| ~FakeWebSocketHandshakeStreamCreateHelper() override = default; |
| std::unique_ptr<WebSocketHandshakeStreamBase> CreateBasicStream( |
| std::unique_ptr<ClientSocketHandle> connect, |
| bool using_proxy, |
| WebSocketEndpointLockManager* websocket_endpoint_lock_manager) override { |
| return nullptr; |
| } |
| std::unique_ptr<WebSocketHandshakeStreamBase> CreateHttp2Stream( |
| base::WeakPtr<SpdySession> session) override { |
| NOTREACHED(); |
| return nullptr; |
| } |
| }; |
| |
| // Returns true if |entry| is not one of the log types paid attention to in this |
| // test. Note that HTTP_CACHE_WRITE_INFO and HTTP_CACHE_*_DATA are |
| // ignored. |
| bool ShouldIgnoreLogEntry(const TestNetLogEntry& entry) { |
| switch (entry.type) { |
| case NetLogEventType::HTTP_CACHE_GET_BACKEND: |
| case NetLogEventType::HTTP_CACHE_OPEN_ENTRY: |
| case NetLogEventType::HTTP_CACHE_CREATE_ENTRY: |
| case NetLogEventType::HTTP_CACHE_ADD_TO_ENTRY: |
| case NetLogEventType::HTTP_CACHE_DOOM_ENTRY: |
| case NetLogEventType::HTTP_CACHE_READ_INFO: |
| return false; |
| default: |
| return true; |
| } |
| } |
| |
| // Modifies |entries| to only include log entries created by the cache layer and |
| // asserted on in these tests. |
| void FilterLogEntries(TestNetLogEntry::List* entries) { |
| base::EraseIf(*entries, ShouldIgnoreLogEntry); |
| } |
| |
| bool LogContainsEventType(const BoundTestNetLog& log, |
| NetLogEventType expected) { |
| TestNetLogEntry::List entries; |
| log.GetEntries(&entries); |
| for (size_t i = 0; i < entries.size(); i++) { |
| if (entries[i].type == expected) |
| return true; |
| } |
| return false; |
| } |
| |
| } // namespace |
| |
| using HttpCacheTest = TestWithScopedTaskEnvironment; |
| |
| //----------------------------------------------------------------------------- |
| // Tests. |
| |
| TEST_F(HttpCacheTest, CreateThenDestroy) { |
| MockHttpCache cache; |
| |
| std::unique_ptr<HttpTransaction> trans; |
| EXPECT_THAT(cache.CreateTransaction(&trans), IsOk()); |
| ASSERT_TRUE(trans.get()); |
| } |
| |
| TEST_F(HttpCacheTest, GetBackend) { |
| MockHttpCache cache(HttpCache::DefaultBackend::InMemory(0)); |
| |
| disk_cache::Backend* backend; |
| TestCompletionCallback cb; |
| // This will lazily initialize the backend. |
| int rv = cache.http_cache()->GetBackend(&backend, cb.callback()); |
| EXPECT_THAT(cb.GetResult(rv), IsOk()); |
| } |
| |
| TEST_F(HttpCacheTest, SimpleGET) { |
| MockHttpCache cache; |
| BoundTestNetLog log; |
| LoadTimingInfo load_timing_info; |
| |
| // Write to the cache. |
| RunTransactionTestAndGetTiming(cache.http_cache(), kSimpleGET_Transaction, |
| log.bound(), &load_timing_info); |
| |
| EXPECT_EQ(1, cache.network_layer()->transaction_count()); |
| EXPECT_EQ(0, cache.disk_cache()->open_count()); |
| EXPECT_EQ(1, cache.disk_cache()->create_count()); |
| TestLoadTimingNetworkRequest(load_timing_info); |
| } |
| |
| TEST_F(HttpCacheTest, SimpleGETNoDiskCache) { |
| MockHttpCache cache; |
| |
| cache.disk_cache()->set_fail_requests(); |
| |
| BoundTestNetLog log; |
| LoadTimingInfo load_timing_info; |
| |
| // Read from the network, and don't use the cache. |
| RunTransactionTestAndGetTiming(cache.http_cache(), kSimpleGET_Transaction, |
| log.bound(), &load_timing_info); |
| |
| // Check that the NetLog was filled as expected. |
| // (We attempted to both Open and Create entries, but both failed). |
| TestNetLogEntry::List entries; |
| log.GetEntries(&entries); |
| FilterLogEntries(&entries); |
| |
| EXPECT_EQ(6u, entries.size()); |
| EXPECT_TRUE(LogContainsBeginEvent(entries, 0, |
| NetLogEventType::HTTP_CACHE_GET_BACKEND)); |
| EXPECT_TRUE( |
| LogContainsEndEvent(entries, 1, NetLogEventType::HTTP_CACHE_GET_BACKEND)); |
| EXPECT_TRUE(LogContainsBeginEvent(entries, 2, |
| NetLogEventType::HTTP_CACHE_OPEN_ENTRY)); |
| EXPECT_TRUE( |
| LogContainsEndEvent(entries, 3, NetLogEventType::HTTP_CACHE_OPEN_ENTRY)); |
| EXPECT_TRUE(LogContainsBeginEvent(entries, 4, |
| NetLogEventType::HTTP_CACHE_CREATE_ENTRY)); |
| EXPECT_TRUE(LogContainsEndEvent(entries, 5, |
| NetLogEventType::HTTP_CACHE_CREATE_ENTRY)); |
| |
| EXPECT_EQ(1, cache.network_layer()->transaction_count()); |
| EXPECT_EQ(0, cache.disk_cache()->open_count()); |
| EXPECT_EQ(0, cache.disk_cache()->create_count()); |
| TestLoadTimingNetworkRequest(load_timing_info); |
| } |
| |
| TEST_F(HttpCacheTest, SimpleGETNoDiskCache2) { |
| // This will initialize a cache object with NULL backend. |
| auto factory = std::make_unique<MockBlockingBackendFactory>(); |
| factory->set_fail(true); |
| factory->FinishCreation(); // We'll complete synchronously. |
| MockHttpCache cache(std::move(factory)); |
| |
| // Read from the network, and don't use the cache. |
| RunTransactionTest(cache.http_cache(), kSimpleGET_Transaction); |
| |
| EXPECT_EQ(1, cache.network_layer()->transaction_count()); |
| EXPECT_FALSE(cache.http_cache()->GetCurrentBackend()); |
| } |
| |
| // Tests that IOBuffers are not referenced after IO completes. |
| TEST_F(HttpCacheTest, ReleaseBuffer) { |
| MockHttpCache cache; |
| |
| // Write to the cache. |
| RunTransactionTest(cache.http_cache(), kSimpleGET_Transaction); |
| |
| MockHttpRequest request(kSimpleGET_Transaction); |
| std::unique_ptr<HttpTransaction> trans; |
| ASSERT_THAT(cache.CreateTransaction(&trans), IsOk()); |
| |
| const int kBufferSize = 10; |
| scoped_refptr<IOBuffer> buffer = base::MakeRefCounted<IOBuffer>(kBufferSize); |
| ReleaseBufferCompletionCallback cb(buffer.get()); |
| |
| int rv = trans->Start(&request, cb.callback(), NetLogWithSource()); |
| EXPECT_THAT(cb.GetResult(rv), IsOk()); |
| |
| rv = trans->Read(buffer.get(), kBufferSize, cb.callback()); |
| EXPECT_EQ(kBufferSize, cb.GetResult(rv)); |
| } |
| |
| TEST_F(HttpCacheTest, SimpleGETWithDiskFailures) { |
| MockHttpCache cache; |
| base::HistogramTester histograms; |
| const std::string histogram_name = "HttpCache.ParallelWritingPattern"; |
| |
| cache.disk_cache()->set_soft_failures(true); |
| |
| // Read from the network, and fail to write to the cache. |
| RunTransactionTest(cache.http_cache(), kSimpleGET_Transaction); |
| |
| EXPECT_EQ(1, cache.network_layer()->transaction_count()); |
| EXPECT_EQ(0, cache.disk_cache()->open_count()); |
| EXPECT_EQ(1, cache.disk_cache()->create_count()); |
| |
| // This one should see an empty cache again. |
| RunTransactionTest(cache.http_cache(), kSimpleGET_Transaction); |
| |
| EXPECT_EQ(2, cache.network_layer()->transaction_count()); |
| EXPECT_EQ(0, cache.disk_cache()->open_count()); |
| EXPECT_EQ(2, cache.disk_cache()->create_count()); |
| |
| // Since the transactions were in headers phase when failed, |
| // PARALLEL_WRITING_NONE should be logged. |
| histograms.ExpectBucketCount( |
| histogram_name, static_cast<int>(HttpCache::PARALLEL_WRITING_NONE), 2); |
| } |
| |
| // Tests that disk failures after the transaction has started don't cause the |
| // request to fail. |
| TEST_F(HttpCacheTest, SimpleGETWithDiskFailures2) { |
| MockHttpCache cache; |
| |
| MockHttpRequest request(kSimpleGET_Transaction); |
| |
| auto c = std::make_unique<Context>(); |
| int rv = cache.CreateTransaction(&c->trans); |
| ASSERT_THAT(rv, IsOk()); |
| |
| rv = c->trans->Start(&request, c->callback.callback(), NetLogWithSource()); |
| EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); |
| rv = c->callback.WaitForResult(); |
| |
| // Start failing request now. |
| cache.disk_cache()->set_soft_failures(true); |
| |
| // We have to open the entry again to propagate the failure flag. |
| disk_cache::Entry* en; |
| ASSERT_TRUE(cache.OpenBackendEntry(kSimpleGET_Transaction.url, &en)); |
| en->Close(); |
| |
| ReadAndVerifyTransaction(c->trans.get(), kSimpleGET_Transaction); |
| c.reset(); |
| |
| EXPECT_EQ(1, cache.network_layer()->transaction_count()); |
| EXPECT_EQ(1, cache.disk_cache()->open_count()); |
| EXPECT_EQ(1, cache.disk_cache()->create_count()); |
| |
| // This one should see an empty cache again. |
| RunTransactionTest(cache.http_cache(), kSimpleGET_Transaction); |
| |
| EXPECT_EQ(2, cache.network_layer()->transaction_count()); |
| EXPECT_EQ(1, cache.disk_cache()->open_count()); |
| EXPECT_EQ(2, cache.disk_cache()->create_count()); |
| } |
| |
| // Tests that we handle failures to read from the cache. |
| TEST_F(HttpCacheTest, SimpleGETWithDiskFailures3) { |
| MockHttpCache cache; |
| |
| // Read from the network, and write to the cache. |
| RunTransactionTest(cache.http_cache(), kSimpleGET_Transaction); |
| |
| EXPECT_EQ(1, cache.network_layer()->transaction_count()); |
| EXPECT_EQ(0, cache.disk_cache()->open_count()); |
| EXPECT_EQ(1, cache.disk_cache()->create_count()); |
| |
| cache.disk_cache()->set_soft_failures(true); |
| |
| MockHttpRequest request(kSimpleGET_Transaction); |
| |
| // Now fail to read from the cache. |
| auto c = std::make_unique<Context>(); |
| int rv = cache.CreateTransaction(&c->trans); |
| ASSERT_THAT(rv, IsOk()); |
| |
| rv = c->trans->Start(&request, c->callback.callback(), NetLogWithSource()); |
| EXPECT_THAT(c->callback.GetResult(rv), IsOk()); |
| |
| // Now verify that the entry was removed from the cache. |
| cache.disk_cache()->set_soft_failures(false); |
| |
| EXPECT_EQ(2, cache.network_layer()->transaction_count()); |
| EXPECT_EQ(1, cache.disk_cache()->open_count()); |
| EXPECT_EQ(2, cache.disk_cache()->create_count()); |
| |
| RunTransactionTest(cache.http_cache(), kSimpleGET_Transaction); |
| |
| EXPECT_EQ(3, cache.network_layer()->transaction_count()); |
| EXPECT_EQ(1, cache.disk_cache()->open_count()); |
| EXPECT_EQ(3, cache.disk_cache()->create_count()); |
| } |
| |
| TEST_F(HttpCacheTest, SimpleGET_LoadOnlyFromCache_Hit) { |
| MockHttpCache cache; |
| |
| BoundTestNetLog log; |
| LoadTimingInfo load_timing_info; |
| |
| // Write to the cache. |
| RunTransactionTestAndGetTiming(cache.http_cache(), kSimpleGET_Transaction, |
| log.bound(), &load_timing_info); |
| |
| // Check that the NetLog was filled as expected. |
| TestNetLogEntry::List entries; |
| log.GetEntries(&entries); |
| FilterLogEntries(&entries); |
| |
| EXPECT_EQ(8u, entries.size()); |
| EXPECT_TRUE(LogContainsBeginEvent(entries, 0, |
| NetLogEventType::HTTP_CACHE_GET_BACKEND)); |
| EXPECT_TRUE( |
| LogContainsEndEvent(entries, 1, NetLogEventType::HTTP_CACHE_GET_BACKEND)); |
| EXPECT_TRUE(LogContainsBeginEvent(entries, 2, |
| NetLogEventType::HTTP_CACHE_OPEN_ENTRY)); |
| EXPECT_TRUE( |
| LogContainsEndEvent(entries, 3, NetLogEventType::HTTP_CACHE_OPEN_ENTRY)); |
| EXPECT_TRUE(LogContainsBeginEvent(entries, 4, |
| NetLogEventType::HTTP_CACHE_CREATE_ENTRY)); |
| EXPECT_TRUE(LogContainsEndEvent(entries, 5, |
| NetLogEventType::HTTP_CACHE_CREATE_ENTRY)); |
| EXPECT_TRUE(LogContainsBeginEvent(entries, 6, |
| NetLogEventType::HTTP_CACHE_ADD_TO_ENTRY)); |
| EXPECT_TRUE(LogContainsEndEvent(entries, 7, |
| NetLogEventType::HTTP_CACHE_ADD_TO_ENTRY)); |
| |
| TestLoadTimingNetworkRequest(load_timing_info); |
| |
| // Force this transaction to read from the cache. |
| MockTransaction transaction(kSimpleGET_Transaction); |
| transaction.load_flags |= LOAD_ONLY_FROM_CACHE | LOAD_SKIP_CACHE_VALIDATION; |
| |
| log.Clear(); |
| |
| RunTransactionTestAndGetTiming(cache.http_cache(), transaction, log.bound(), |
| &load_timing_info); |
| |
| // Check that the NetLog was filled as expected. |
| log.GetEntries(&entries); |
| FilterLogEntries(&entries); |
| |
| EXPECT_EQ(8u, entries.size()); |
| EXPECT_TRUE(LogContainsBeginEvent(entries, 0, |
| NetLogEventType::HTTP_CACHE_GET_BACKEND)); |
| EXPECT_TRUE( |
| LogContainsEndEvent(entries, 1, NetLogEventType::HTTP_CACHE_GET_BACKEND)); |
| EXPECT_TRUE(LogContainsBeginEvent(entries, 2, |
| NetLogEventType::HTTP_CACHE_OPEN_ENTRY)); |
| EXPECT_TRUE( |
| LogContainsEndEvent(entries, 3, NetLogEventType::HTTP_CACHE_OPEN_ENTRY)); |
| EXPECT_TRUE(LogContainsBeginEvent(entries, 4, |
| NetLogEventType::HTTP_CACHE_ADD_TO_ENTRY)); |
| EXPECT_TRUE(LogContainsEndEvent(entries, 5, |
| NetLogEventType::HTTP_CACHE_ADD_TO_ENTRY)); |
| EXPECT_TRUE( |
| LogContainsBeginEvent(entries, 6, NetLogEventType::HTTP_CACHE_READ_INFO)); |
| EXPECT_TRUE( |
| LogContainsEndEvent(entries, 7, NetLogEventType::HTTP_CACHE_READ_INFO)); |
| |
| EXPECT_EQ(1, cache.network_layer()->transaction_count()); |
| EXPECT_EQ(1, cache.disk_cache()->open_count()); |
| EXPECT_EQ(1, cache.disk_cache()->create_count()); |
| TestLoadTimingCachedResponse(load_timing_info); |
| } |
| |
| TEST_F(HttpCacheTest, SimpleGET_LoadOnlyFromCache_Miss) { |
| MockHttpCache cache; |
| |
| // force this transaction to read from the cache |
| MockTransaction transaction(kSimpleGET_Transaction); |
| transaction.load_flags |= LOAD_ONLY_FROM_CACHE | LOAD_SKIP_CACHE_VALIDATION; |
| |
| MockHttpRequest request(transaction); |
| TestCompletionCallback callback; |
| |
| std::unique_ptr<HttpTransaction> trans; |
| ASSERT_THAT(cache.CreateTransaction(&trans), IsOk()); |
| |
| int rv = trans->Start(&request, callback.callback(), NetLogWithSource()); |
| if (rv == ERR_IO_PENDING) |
| rv = callback.WaitForResult(); |
| ASSERT_THAT(rv, IsError(ERR_CACHE_MISS)); |
| |
| trans.reset(); |
| |
| EXPECT_EQ(0, cache.network_layer()->transaction_count()); |
| EXPECT_EQ(0, cache.disk_cache()->open_count()); |
| EXPECT_EQ(0, cache.disk_cache()->create_count()); |
| } |
| |
| TEST_F(HttpCacheTest, SimpleGET_LoadPreferringCache_Hit) { |
| MockHttpCache cache; |
| base::HistogramTester histograms; |
| const std::string histogram_name = "HttpCache.ParallelWritingPattern"; |
| |
| // write to the cache |
| RunTransactionTest(cache.http_cache(), kSimpleGET_Transaction); |
| |
| // force this transaction to read from the cache if valid |
| MockTransaction transaction(kSimpleGET_Transaction); |
| transaction.load_flags |= LOAD_SKIP_CACHE_VALIDATION; |
| |
| RunTransactionTest(cache.http_cache(), transaction); |
| |
| EXPECT_EQ(1, cache.network_layer()->transaction_count()); |
| EXPECT_EQ(1, cache.disk_cache()->open_count()); |
| EXPECT_EQ(1, cache.disk_cache()->create_count()); |
| |
| histograms.ExpectBucketCount( |
| histogram_name, static_cast<int>(HttpCache::PARALLEL_WRITING_CREATE), 1); |
| histograms.ExpectBucketCount( |
| histogram_name, |
| static_cast<int>(HttpCache::PARALLEL_WRITING_NONE_CACHE_READ), 1); |
| } |
| |
| TEST_F(HttpCacheTest, SimpleGET_LoadPreferringCache_Miss) { |
| MockHttpCache cache; |
| |
| // force this transaction to read from the cache if valid |
| MockTransaction transaction(kSimpleGET_Transaction); |
| transaction.load_flags |= LOAD_SKIP_CACHE_VALIDATION; |
| |
| RunTransactionTest(cache.http_cache(), transaction); |
| |
| EXPECT_EQ(1, cache.network_layer()->transaction_count()); |
| EXPECT_EQ(0, cache.disk_cache()->open_count()); |
| EXPECT_EQ(1, cache.disk_cache()->create_count()); |
| } |
| |
| // Tests LOAD_SKIP_CACHE_VALIDATION in the presence of vary headers. |
| TEST_F(HttpCacheTest, SimpleGET_LoadPreferringCache_VaryMatch) { |
| MockHttpCache cache; |
| |
| // Write to the cache. |
| MockTransaction transaction(kSimpleGET_Transaction); |
| transaction.request_headers = "Foo: bar\r\n"; |
| transaction.response_headers = "Cache-Control: max-age=10000\n" |
| "Vary: Foo\n"; |
| AddMockTransaction(&transaction); |
| RunTransactionTest(cache.http_cache(), transaction); |
| |
| // Read from the cache. |
| transaction.load_flags |= LOAD_SKIP_CACHE_VALIDATION; |
| RunTransactionTest(cache.http_cache(), transaction); |
| |
| EXPECT_EQ(1, cache.network_layer()->transaction_count()); |
| EXPECT_EQ(1, cache.disk_cache()->open_count()); |
| EXPECT_EQ(1, cache.disk_cache()->create_count()); |
| RemoveMockTransaction(&transaction); |
| } |
| |
| // Tests LOAD_SKIP_CACHE_VALIDATION in the presence of vary headers. |
| TEST_F(HttpCacheTest, SimpleGET_LoadPreferringCache_VaryMismatch) { |
| MockHttpCache cache; |
| |
| // Write to the cache. |
| MockTransaction transaction(kSimpleGET_Transaction); |
| transaction.request_headers = "Foo: bar\r\n"; |
| transaction.response_headers = "Cache-Control: max-age=10000\n" |
| "Vary: Foo\n"; |
| AddMockTransaction(&transaction); |
| RunTransactionTest(cache.http_cache(), transaction); |
| |
| // Attempt to read from the cache... this is a vary mismatch that must reach |
| // the network again. |
| transaction.load_flags |= LOAD_SKIP_CACHE_VALIDATION; |
| transaction.request_headers = "Foo: none\r\n"; |
| BoundTestNetLog log; |
| LoadTimingInfo load_timing_info; |
| RunTransactionTestAndGetTiming(cache.http_cache(), transaction, log.bound(), |
| &load_timing_info); |
| |
| EXPECT_EQ(2, cache.network_layer()->transaction_count()); |
| EXPECT_EQ(1, cache.disk_cache()->open_count()); |
| EXPECT_EQ(1, cache.disk_cache()->create_count()); |
| TestLoadTimingNetworkRequest(load_timing_info); |
| RemoveMockTransaction(&transaction); |
| } |
| |
| // Tests that we honor Vary: * with LOAD_SKIP_CACHE_VALIDATION (crbug/778681) |
| TEST_F(HttpCacheTest, SimpleGET_LoadSkipCacheValidation_VaryStar) { |
| MockHttpCache cache; |
| |
| // Write to the cache. |
| MockTransaction transaction(kSimpleGET_Transaction); |
| transaction.response_headers = |
| "Cache-Control: max-age=10000\n" |
| "Vary: *\n"; |
| AddMockTransaction(&transaction); |
| RunTransactionTest(cache.http_cache(), transaction); |
| |
| // Attempt to read from the cache... we will still load it from network, |
| // since Vary: * doesn't match. |
| transaction.load_flags |= LOAD_SKIP_CACHE_VALIDATION; |
| BoundTestNetLog log; |
| LoadTimingInfo load_timing_info; |
| RunTransactionTestAndGetTiming(cache.http_cache(), transaction, log.bound(), |
| &load_timing_info); |
| |
| EXPECT_EQ(2, cache.network_layer()->transaction_count()); |
| EXPECT_EQ(1, cache.disk_cache()->open_count()); |
| EXPECT_EQ(1, cache.disk_cache()->create_count()); |
| RemoveMockTransaction(&transaction); |
| } |
| |
| // Tests that was_cached was set properly on a failure, even if the cached |
| // response wasn't returned. |
| TEST_F(HttpCacheTest, SimpleGET_CacheSignal_Failure) { |
| for (bool use_memory_entry_data : {false, true}) { |
| MockHttpCache cache; |
| cache.disk_cache()->set_support_in_memory_entry_data(use_memory_entry_data); |
| |
| // Prime cache. |
| MockTransaction transaction(kSimpleGET_Transaction); |
| transaction.response_headers = "Cache-Control: no-cache\n"; |
| |
| AddMockTransaction(&transaction); |
| RunTransactionTest(cache.http_cache(), transaction); |
| EXPECT_EQ(1, cache.network_layer()->transaction_count()); |
| EXPECT_EQ(1, cache.disk_cache()->create_count()); |
| EXPECT_EQ(0, cache.disk_cache()->open_count()); |
| RemoveMockTransaction(&transaction); |
| |
| // Network failure with error; should fail but have was_cached set. |
| transaction.start_return_code = ERR_FAILED; |
| AddMockTransaction(&transaction); |
| |
| MockHttpRequest request(transaction); |
| TestCompletionCallback callback; |
| std::unique_ptr<HttpTransaction> trans; |
| int rv = cache.http_cache()->CreateTransaction(DEFAULT_PRIORITY, &trans); |
| EXPECT_THAT(rv, IsOk()); |
| ASSERT_TRUE(trans.get()); |
| rv = trans->Start(&request, callback.callback(), NetLogWithSource()); |
| EXPECT_THAT(callback.GetResult(rv), IsError(ERR_FAILED)); |
| |
| const HttpResponseInfo* response_info = trans->GetResponseInfo(); |
| ASSERT_TRUE(response_info); |
| // If use_memory_entry_data is true, we will not bother opening the entry, |
| // and just kick it out, so was_cached will end up false. |
| EXPECT_EQ(2, cache.network_layer()->transaction_count()); |
| if (use_memory_entry_data) { |
| EXPECT_EQ(false, response_info->was_cached); |
| EXPECT_EQ(2, cache.disk_cache()->create_count()); |
| EXPECT_EQ(0, cache.disk_cache()->open_count()); |
| } else { |
| EXPECT_EQ(true, response_info->was_cached); |
| EXPECT_EQ(1, cache.disk_cache()->create_count()); |
| EXPECT_EQ(1, cache.disk_cache()->open_count()); |
| } |
| |
| RemoveMockTransaction(&transaction); |
| } |
| } |
| |
| // Confirm if we have an empty cache, a read is marked as network verified. |
| TEST_F(HttpCacheTest, SimpleGET_NetworkAccessed_Network) { |
| MockHttpCache cache; |
| |
| // write to the cache |
| HttpResponseInfo response_info; |
| RunTransactionTestWithResponseInfo(cache.http_cache(), kSimpleGET_Transaction, |
| &response_info); |
| |
| EXPECT_EQ(1, cache.network_layer()->transaction_count()); |
| EXPECT_EQ(0, cache.disk_cache()->open_count()); |
| EXPECT_EQ(1, cache.disk_cache()->create_count()); |
| EXPECT_TRUE(response_info.network_accessed); |
| EXPECT_EQ(CacheEntryStatus::ENTRY_NOT_IN_CACHE, |
| response_info.cache_entry_status); |
| } |
| |
| // Confirm if we have a fresh entry in cache, it isn't marked as |
| // network verified. |
| TEST_F(HttpCacheTest, SimpleGET_NetworkAccessed_Cache) { |
| MockHttpCache cache; |
| |
| // Prime cache. |
| MockTransaction transaction(kSimpleGET_Transaction); |
| |
| RunTransactionTest(cache.http_cache(), transaction); |
| EXPECT_EQ(1, cache.network_layer()->transaction_count()); |
| EXPECT_EQ(1, cache.disk_cache()->create_count()); |
| |
| // Re-run transaction; make sure we don't mark the network as accessed. |
| HttpResponseInfo response_info; |
| RunTransactionTestWithResponseInfo(cache.http_cache(), transaction, |
| &response_info); |
| |
| EXPECT_EQ(1, cache.network_layer()->transaction_count()); |
| EXPECT_FALSE(response_info.server_data_unavailable); |
| EXPECT_FALSE(response_info.network_accessed); |
| EXPECT_EQ(CacheEntryStatus::ENTRY_USED, response_info.cache_entry_status); |
| } |
| |
| TEST_F(HttpCacheTest, SimpleGET_LoadBypassCache) { |
| MockHttpCache cache; |
| |
| // Write to the cache. |
| RunTransactionTest(cache.http_cache(), kSimpleGET_Transaction); |
| |
| // Force this transaction to write to the cache again. |
| MockTransaction transaction(kSimpleGET_Transaction); |
| transaction.load_flags |= LOAD_BYPASS_CACHE; |
| |
| BoundTestNetLog log; |
| LoadTimingInfo load_timing_info; |
| |
| // Write to the cache. |
| RunTransactionTestAndGetTiming(cache.http_cache(), transaction, log.bound(), |
| &load_timing_info); |
| |
| // Check that the NetLog was filled as expected. |
| TestNetLogEntry::List entries; |
| log.GetEntries(&entries); |
| FilterLogEntries(&entries); |
| |
| EXPECT_EQ(8u, entries.size()); |
| EXPECT_TRUE(LogContainsBeginEvent(entries, 0, |
| NetLogEventType::HTTP_CACHE_GET_BACKEND)); |
| EXPECT_TRUE( |
| LogContainsEndEvent(entries, 1, NetLogEventType::HTTP_CACHE_GET_BACKEND)); |
| EXPECT_TRUE(LogContainsBeginEvent(entries, 2, |
| NetLogEventType::HTTP_CACHE_DOOM_ENTRY)); |
| EXPECT_TRUE( |
| LogContainsEndEvent(entries, 3, NetLogEventType::HTTP_CACHE_DOOM_ENTRY)); |
| EXPECT_TRUE(LogContainsBeginEvent(entries, 4, |
| NetLogEventType::HTTP_CACHE_CREATE_ENTRY)); |
| EXPECT_TRUE(LogContainsEndEvent(entries, 5, |
| NetLogEventType::HTTP_CACHE_CREATE_ENTRY)); |
| EXPECT_TRUE(LogContainsBeginEvent(entries, 6, |
| NetLogEventType::HTTP_CACHE_ADD_TO_ENTRY)); |
| EXPECT_TRUE(LogContainsEndEvent(entries, 7, |
| NetLogEventType::HTTP_CACHE_ADD_TO_ENTRY)); |
| |
| EXPECT_EQ(2, cache.network_layer()->transaction_count()); |
| EXPECT_EQ(0, cache.disk_cache()->open_count()); |
| EXPECT_EQ(2, cache.disk_cache()->create_count()); |
| TestLoadTimingNetworkRequest(load_timing_info); |
| } |
| |
| TEST_F(HttpCacheTest, SimpleGET_LoadBypassCache_Implicit) { |
| MockHttpCache cache; |
| |
| // write to the cache |
| RunTransactionTest(cache.http_cache(), kSimpleGET_Transaction); |
| |
| // force this transaction to write to the cache again |
| MockTransaction transaction(kSimpleGET_Transaction); |
| transaction.request_headers = "pragma: no-cache\r\n"; |
| |
| RunTransactionTest(cache.http_cache(), transaction); |
| |
| EXPECT_EQ(2, cache.network_layer()->transaction_count()); |
| EXPECT_EQ(0, cache.disk_cache()->open_count()); |
| EXPECT_EQ(2, cache.disk_cache()->create_count()); |
| } |
| |
| TEST_F(HttpCacheTest, SimpleGET_LoadBypassCache_Implicit2) { |
| MockHttpCache cache; |
| |
| // write to the cache |
| RunTransactionTest(cache.http_cache(), kSimpleGET_Transaction); |
| |
| // force this transaction to write to the cache again |
| MockTransaction transaction(kSimpleGET_Transaction); |
| transaction.request_headers = "cache-control: no-cache\r\n"; |
| |
| RunTransactionTest(cache.http_cache(), transaction); |
| |
| EXPECT_EQ(2, cache.network_layer()->transaction_count()); |
| EXPECT_EQ(0, cache.disk_cache()->open_count()); |
| EXPECT_EQ(2, cache.disk_cache()->create_count()); |
| } |
| |
| TEST_F(HttpCacheTest, SimpleGET_LoadValidateCache) { |
| MockHttpCache cache; |
| |
| // Write to the cache. |
| RunTransactionTest(cache.http_cache(), kSimpleGET_Transaction); |
| |
| // Read from the cache. |
| RunTransactionTest(cache.http_cache(), kSimpleGET_Transaction); |
| |
| // Force this transaction to validate the cache. |
| MockTransaction transaction(kSimpleGET_Transaction); |
| transaction.load_flags |= LOAD_VALIDATE_CACHE; |
| |
| HttpResponseInfo response_info; |
| BoundTestNetLog log; |
| LoadTimingInfo load_timing_info; |
| RunTransactionTestWithResponseInfoAndGetTiming( |
| cache.http_cache(), transaction, &response_info, log.bound(), |
| &load_timing_info); |
| |
| EXPECT_EQ(2, cache.network_layer()->transaction_count()); |
| EXPECT_EQ(1, cache.disk_cache()->open_count()); |
| EXPECT_EQ(1, cache.disk_cache()->create_count()); |
| EXPECT_TRUE(response_info.network_accessed); |
| TestLoadTimingNetworkRequest(load_timing_info); |
| } |
| |
| TEST_F(HttpCacheTest, SimpleGET_LoadValidateCache_Implicit) { |
| MockHttpCache cache; |
| |
| // write to the cache |
| RunTransactionTest(cache.http_cache(), kSimpleGET_Transaction); |
| |
| // read from the cache |
| RunTransactionTest(cache.http_cache(), kSimpleGET_Transaction); |
| |
| // force this transaction to validate the cache |
| MockTransaction transaction(kSimpleGET_Transaction); |
| transaction.request_headers = "cache-control: max-age=0\r\n"; |
| |
| RunTransactionTest(cache.http_cache(), transaction); |
| |
| EXPECT_EQ(2, cache.network_layer()->transaction_count()); |
| EXPECT_EQ(1, cache.disk_cache()->open_count()); |
| EXPECT_EQ(1, cache.disk_cache()->create_count()); |
| } |
| |
| // Tests that |unused_since_prefetch| is updated accordingly (e.g. it is set to |
| // true after a prefetch and set back to false when the prefetch is used). |
| TEST_F(HttpCacheTest, SimpleGET_UnusedSincePrefetch) { |
| MockHttpCache cache; |
| HttpResponseInfo response_info; |
| |
| // A normal load does not have |unused_since_prefetch| set. |
| RunTransactionTestWithResponseInfoAndGetTiming( |
| cache.http_cache(), kSimpleGET_Transaction, &response_info, |
| BoundTestNetLog().bound(), nullptr); |
| EXPECT_FALSE(response_info.unused_since_prefetch); |
| EXPECT_FALSE(response_info.was_cached); |
| |
| // The prefetch itself does not have |unused_since_prefetch| set. |
| MockTransaction prefetch_transaction(kSimpleGET_Transaction); |
| prefetch_transaction.load_flags |= LOAD_PREFETCH; |
| RunTransactionTestWithResponseInfoAndGetTiming( |
| cache.http_cache(), prefetch_transaction, &response_info, |
| BoundTestNetLog().bound(), nullptr); |
| EXPECT_FALSE(response_info.unused_since_prefetch); |
| EXPECT_TRUE(response_info.was_cached); |
| |
| // A duplicated prefetch has |unused_since_prefetch| set. |
| RunTransactionTestWithResponseInfoAndGetTiming( |
| cache.http_cache(), prefetch_transaction, &response_info, |
| BoundTestNetLog().bound(), nullptr); |
| EXPECT_TRUE(response_info.unused_since_prefetch); |
| EXPECT_TRUE(response_info.was_cached); |
| |
| // |unused_since_prefetch| is still true after two prefetches in a row. |
| RunTransactionTestWithResponseInfoAndGetTiming( |
| cache.http_cache(), kSimpleGET_Transaction, &response_info, |
| BoundTestNetLog().bound(), nullptr); |
| EXPECT_TRUE(response_info.unused_since_prefetch); |
| EXPECT_TRUE(response_info.was_cached); |
| |
| // The resource has now been used, back to normal behavior. |
| RunTransactionTestWithResponseInfoAndGetTiming( |
| cache.http_cache(), kSimpleGET_Transaction, &response_info, |
| BoundTestNetLog().bound(), nullptr); |
| EXPECT_FALSE(response_info.unused_since_prefetch); |
| EXPECT_TRUE(response_info.was_cached); |
| } |
| |
| static void PreserveRequestHeaders_Handler(const HttpRequestInfo* request, |
| std::string* response_status, |
| std::string* response_headers, |
| std::string* response_data) { |
| EXPECT_TRUE(request->extra_headers.HasHeader(kExtraHeaderKey)); |
| } |
| |
| // Tests that we don't remove extra headers for simple requests. |
| TEST_F(HttpCacheTest, SimpleGET_PreserveRequestHeaders) { |
| for (bool use_memory_entry_data : {false, true}) { |
| MockHttpCache cache; |
| cache.disk_cache()->set_support_in_memory_entry_data(use_memory_entry_data); |
| |
| MockTransaction transaction(kSimpleGET_Transaction); |
| transaction.handler = PreserveRequestHeaders_Handler; |
| transaction.request_headers = EXTRA_HEADER; |
| transaction.response_headers = "Cache-Control: max-age=0\n"; |
| AddMockTransaction(&transaction); |
| |
| // Write, then revalidate the entry. |
| RunTransactionTest(cache.http_cache(), transaction); |
| RunTransactionTest(cache.http_cache(), transaction); |
| |
| EXPECT_EQ(2, cache.network_layer()->transaction_count()); |
| |
| // If the backend supports memory entry data, we can figure out that the |
| // entry has caching-hostile headers w/o opening it. |
| if (use_memory_entry_data) { |
| EXPECT_EQ(0, cache.disk_cache()->open_count()); |
| EXPECT_EQ(2, cache.disk_cache()->create_count()); |
| } else { |
| EXPECT_EQ(1, cache.disk_cache()->open_count()); |
| EXPECT_EQ(1, cache.disk_cache()->create_count()); |
| } |
| RemoveMockTransaction(&transaction); |
| } |
| } |
| |
| // Tests that we don't remove extra headers for conditionalized requests. |
| TEST_F(HttpCacheTest, ConditionalizedGET_PreserveRequestHeaders) { |
| for (bool use_memory_entry_data : {false, true}) { |
| MockHttpCache cache; |
| // Unlike in SimpleGET_PreserveRequestHeaders, this entry can be |
| // conditionalized, so memory hints don't affect behavior. |
| cache.disk_cache()->set_support_in_memory_entry_data(use_memory_entry_data); |
| |
| // Write to the cache. |
| RunTransactionTest(cache.http_cache(), kETagGET_Transaction); |
| |
| MockTransaction transaction(kETagGET_Transaction); |
| transaction.handler = PreserveRequestHeaders_Handler; |
| transaction.request_headers = "If-None-Match: \"foopy\"\r\n" EXTRA_HEADER; |
| AddMockTransaction(&transaction); |
| |
| RunTransactionTest(cache.http_cache(), transaction); |
| |
| EXPECT_EQ(2, cache.network_layer()->transaction_count()); |
| EXPECT_EQ(1, cache.disk_cache()->open_count()); |
| EXPECT_EQ(1, cache.disk_cache()->create_count()); |
| RemoveMockTransaction(&transaction); |
| } |
| } |
| |
| TEST_F(HttpCacheTest, SimpleGET_ManyReaders) { |
| MockHttpCache cache; |
| |
| MockHttpRequest request(kSimpleGET_Transaction); |
| |
| std::vector<std::unique_ptr<Context>> context_list; |
| const int kNumTransactions = 5; |
| |
| for (int i = 0; i < kNumTransactions; ++i) { |
| context_list.push_back(std::make_unique<Context>()); |
| auto& c = context_list[i]; |
| |
| c->result = cache.CreateTransaction(&c->trans); |
| ASSERT_THAT(c->result, IsOk()); |
| EXPECT_EQ(LOAD_STATE_IDLE, c->trans->GetLoadState()); |
| |
| c->result = |
| c->trans->Start(&request, c->callback.callback(), NetLogWithSource()); |
| } |
| |
| // All requests are waiting for the active entry. |
| for (auto& context : context_list) { |
| EXPECT_EQ(LOAD_STATE_WAITING_FOR_CACHE, context->trans->GetLoadState()); |
| } |
| |
| // Allow all requests to move from the Create queue to the active entry. |
| base::RunLoop().RunUntilIdle(); |
| |
| // All requests are added to writers. |
| EXPECT_EQ(kNumTransactions, |
| cache.GetCountWriterTransactions(kSimpleGET_Transaction.url)); |
| |
| EXPECT_EQ(1, cache.network_layer()->transaction_count()); |
| EXPECT_EQ(0, cache.disk_cache()->open_count()); |
| EXPECT_EQ(1, cache.disk_cache()->create_count()); |
| |
| // All requests are between Start and Read, i.e. idle. |
| for (auto& context : context_list) { |
| EXPECT_EQ(LOAD_STATE_IDLE, context->trans->GetLoadState()); |
| } |
| |
| for (int i = 0; i < kNumTransactions; ++i) { |
| auto& c = context_list[i]; |
| if (c->result == ERR_IO_PENDING) |
| c->result = c->callback.WaitForResult(); |
| |
| // After the 1st transaction has completed the response, all transactions |
| // get added to readers. |
| if (i > 0) { |
| EXPECT_FALSE(cache.IsWriterPresent(kSimpleGET_Transaction.url)); |
| EXPECT_EQ(kNumTransactions - i, |
| cache.GetCountReaders(kSimpleGET_Transaction.url)); |
| } |
| |
| ReadAndVerifyTransaction(c->trans.get(), kSimpleGET_Transaction); |
| } |
| |
| // We should not have had to re-open the disk entry |
| EXPECT_EQ(1, cache.network_layer()->transaction_count()); |
| EXPECT_EQ(0, cache.disk_cache()->open_count()); |
| EXPECT_EQ(1, cache.disk_cache()->create_count()); |
| } |
| |
| // Tests that we can have parallel validation on range requests. |
| TEST_F(HttpCacheTest, RangeGET_ParallelValidationNoMatch) { |
| MockHttpCache cache; |
| |
| ScopedMockTransaction transaction(kRangeGET_TransactionOK); |
| MockHttpRequest request(transaction); |
| |
| std::vector<std::unique_ptr<Context>> context_list; |
| const int kNumTransactions = 5; |
| |
| for (int i = 0; i < kNumTransactions; ++i) { |
| context_list.push_back(std::make_unique<Context>()); |
| auto& c = context_list[i]; |
| |
| c->result = cache.CreateTransaction(&c->trans); |
| ASSERT_THAT(c->result, IsOk()); |
| EXPECT_EQ(LOAD_STATE_IDLE, c->trans->GetLoadState()); |
| |
| c->result = |
| c->trans->Start(&request, c->callback.callback(), NetLogWithSource()); |
| } |
| |
| // All requests are waiting for the active entry. |
| for (auto& context : context_list) { |
| EXPECT_EQ(LOAD_STATE_WAITING_FOR_CACHE, context->trans->GetLoadState()); |
| } |
| |
| // Allow all requests to move from the Create queue to the active entry. |
| base::RunLoop().RunUntilIdle(); |
| |
| // First entry created is doomed due to 2nd transaction's validation leading |
| // to restarting of the queued transactions. |
| EXPECT_TRUE(cache.IsWriterPresent(kRangeGET_TransactionOK.url)); |
| |
| // TODO(shivanisha): The restarted transactions race for creating the entry |
| // and thus instead of all 4 succeeding, 2 of them succeed. This is very |
| // implementation specific and happens because the queued transactions get |
| // restarted synchronously and get to the queue of creating the entry before |
| // the transaction that is restarting them. Fix the test to make it less |
| // vulnerable to any scheduling changes in the code. |
| EXPECT_EQ(5, cache.network_layer()->transaction_count()); |
| EXPECT_EQ(0, cache.disk_cache()->open_count()); |
| EXPECT_EQ(3, cache.disk_cache()->create_count()); |
| |
| for (auto& context : context_list) { |
| EXPECT_EQ(LOAD_STATE_IDLE, context->trans->GetLoadState()); |
| } |
| |
| for (int i = 0; i < kNumTransactions; ++i) { |
| auto& c = context_list[i]; |
| if (c->result == ERR_IO_PENDING) |
| c->result = c->callback.WaitForResult(); |
| |
| ReadAndVerifyTransaction(c->trans.get(), kRangeGET_TransactionOK); |
| } |
| |
| EXPECT_EQ(5, cache.network_layer()->transaction_count()); |
| EXPECT_EQ(0, cache.disk_cache()->open_count()); |
| EXPECT_EQ(3, cache.disk_cache()->create_count()); |
| } |
| |
| // Tests that if a transaction is dooming the entry and the entry was doomed by |
| // another transaction that was not part of the entry and created a new entry, |
| // the new entry should not be incorrectly doomed. (crbug.com/736993) |
| TEST_F(HttpCacheTest, RangeGET_ParallelValidationNoMatchDoomEntry) { |
| MockHttpCache cache; |
| |
| ScopedMockTransaction transaction(kRangeGET_TransactionOK); |
| MockHttpRequest request(transaction); |
| |
| MockTransaction dooming_transaction(kRangeGET_TransactionOK); |
| dooming_transaction.load_flags |= LOAD_BYPASS_CACHE; |
| MockHttpRequest dooming_request(dooming_transaction); |
| |
| std::vector<std::unique_ptr<Context>> context_list; |
| const int kNumTransactions = 3; |
| |
| scoped_refptr<MockDiskEntry> first_entry; |
| scoped_refptr<MockDiskEntry> second_entry; |
| for (int i = 0; i < kNumTransactions; ++i) { |
| context_list.push_back(std::make_unique<Context>()); |
| auto& c = context_list[i]; |
| |
| c->result = cache.CreateTransaction(&c->trans); |
| ASSERT_THAT(c->result, IsOk()); |
| EXPECT_EQ(LOAD_STATE_IDLE, c->trans->GetLoadState()); |
| |
| MockHttpRequest* this_request = &request; |
| |
| if (i == 2) |
| this_request = &dooming_request; |
| |
| if (i == 1) { |
| ASSERT_TRUE(first_entry); |
| first_entry->SetDefer(MockDiskEntry::DEFER_READ); |
| } |
| |
| c->result = c->trans->Start(this_request, c->callback.callback(), |
| NetLogWithSource()); |
| |
| // Continue the transactions. 2nd will pause at the cache reading state and |
| // 3rd transaction will doom the entry. |
| base::RunLoop().RunUntilIdle(); |
| |
| // Check status of the first and second entries after every transaction. |
| switch (i) { |
| case 0: |
| first_entry = |
| cache.disk_cache()->GetDiskEntryRef(kRangeGET_TransactionOK.url); |
| break; |
| case 1: |
| EXPECT_FALSE(first_entry->is_doomed()); |
| break; |
| case 2: |
| EXPECT_TRUE(first_entry->is_doomed()); |
| second_entry = |
| cache.disk_cache()->GetDiskEntryRef(kRangeGET_TransactionOK.url); |
| EXPECT_FALSE(second_entry->is_doomed()); |
| break; |
| } |
| } |
| // Resume cache read by 1st transaction which will lead to dooming the entry |
| // as well since the entry cannot be validated. This double dooming should not |
| // lead to an assertion. |
| first_entry->ResumeDiskEntryOperation(); |
| base::RunLoop().RunUntilIdle(); |
| |
| // Since second_entry is already created, when 1st transaction goes on to |
| // create an entry, it will get ERR_CACHE_RACE leading to dooming of |
| // second_entry and creation of a third entry. |
| EXPECT_TRUE(second_entry->is_doomed()); |
| |
| EXPECT_EQ(3, cache.network_layer()->transaction_count()); |
| EXPECT_EQ(0, cache.disk_cache()->open_count()); |
| EXPECT_EQ(3, cache.disk_cache()->create_count()); |
| |
| for (auto& context : context_list) { |
| EXPECT_EQ(LOAD_STATE_IDLE, context->trans->GetLoadState()); |
| } |
| |
| for (auto& c : context_list) { |
| ReadAndVerifyTransaction(c->trans.get(), kRangeGET_TransactionOK); |
| } |
| |
| EXPECT_EQ(3, cache.network_layer()->transaction_count()); |
| EXPECT_EQ(0, cache.disk_cache()->open_count()); |
| EXPECT_EQ(3, cache.disk_cache()->create_count()); |
| } |
| |
| // Same as above but tests that the 2nd transaction does not do anything if |
| // there is nothing to doom. (crbug.com/736993) |
| TEST_F(HttpCacheTest, RangeGET_ParallelValidationNoMatchDoomEntry1) { |
| MockHttpCache cache; |
| |
| ScopedMockTransaction transaction(kRangeGET_TransactionOK); |
| MockHttpRequest request(transaction); |
| |
| MockTransaction dooming_transaction(kRangeGET_TransactionOK); |
| dooming_transaction.load_flags |= LOAD_BYPASS_CACHE; |
| MockHttpRequest dooming_request(dooming_transaction); |
| |
| std::vector<std::unique_ptr<Context>> context_list; |
| const int kNumTransactions = 3; |
| |
| scoped_refptr<MockDiskEntry> first_entry; |
| for (int i = 0; i < kNumTransactions; ++i) { |
| context_list.push_back(std::make_unique<Context>()); |
| auto& c = context_list[i]; |
| |
| c->result = cache.CreateTransaction(&c->trans); |
| ASSERT_THAT(c->result, IsOk()); |
| EXPECT_EQ(LOAD_STATE_IDLE, c->trans->GetLoadState()); |
| |
| MockHttpRequest* this_request = &request; |
| |
| if (i == 2) { |
| this_request = &dooming_request; |
| cache.disk_cache()->SetDefer(MockDiskEntry::DEFER_CREATE); |
| } |
| |
| if (i == 1) { |
| ASSERT_TRUE(first_entry); |
| first_entry->SetDefer(MockDiskEntry::DEFER_READ); |
| } |
| |
| c->result = c->trans->Start(this_request, c->callback.callback(), |
| NetLogWithSource()); |
| |
| // Continue the transactions. 2nd will pause at the cache reading state and |
| // 3rd transaction will doom the entry and pause before creating a new |
| // entry. |
| base::RunLoop().RunUntilIdle(); |
| |
| // Check status of the entry after every transaction. |
| switch (i) { |
| case 0: |
| first_entry = |
| cache.disk_cache()->GetDiskEntryRef(kRangeGET_TransactionOK.url); |
| break; |
| case 1: |
| EXPECT_FALSE(first_entry->is_doomed()); |
| break; |
| case 2: |
| EXPECT_TRUE(first_entry->is_doomed()); |
| break; |
| } |
| } |
| // Resume cache read by 2nd transaction which will lead to dooming the entry |
| // as well since the entry cannot be validated. This double dooming should not |
| // lead to an assertion. |
| first_entry->ResumeDiskEntryOperation(); |
| base::RunLoop().RunUntilIdle(); |
| |
| // Resume creation of entry by 3rd transaction. |
| cache.disk_cache()->ResumeCacheOperation(); |
| base::RunLoop().RunUntilIdle(); |
| |
| // Note that since 3rd transaction's entry is already created but its |
| // callback is deferred, MockDiskCache's implementation returns |
| // ERR_CACHE_CREATE_FAILURE when 2nd transaction tries to create an entry |
| // during that time, leading to it switching over to pass-through mode. |
| // Thus the number of entries is 2 below. |
| EXPECT_EQ(3, cache.network_layer()->transaction_count()); |
| EXPECT_EQ(0, cache.disk_cache()->open_count()); |
| EXPECT_EQ(2, cache.disk_cache()->create_count()); |
| |
| for (auto& context : context_list) { |
| EXPECT_EQ(LOAD_STATE_IDLE, context->trans->GetLoadState()); |
| } |
| |
| for (auto& c : context_list) { |
| ReadAndVerifyTransaction(c->trans.get(), kRangeGET_TransactionOK); |
| } |
| |
| EXPECT_EQ(3, cache.network_layer()->transaction_count()); |
| EXPECT_EQ(0, cache.disk_cache()->open_count()); |
| EXPECT_EQ(2, cache.disk_cache()->create_count()); |
| } |
| |
| // Tests parallel validation on range requests with non-overlapping ranges. |
| TEST_F(HttpCacheTest, RangeGET_ParallelValidationDifferentRanges) { |
| base::HistogramTester histograms; |
| const std::string histogram_name = "HttpCache.ParallelWritingPattern"; |
| MockHttpCache cache; |
| |
| ScopedMockTransaction transaction(kRangeGET_TransactionOK); |
| |
| std::vector<std::unique_ptr<Context>> context_list; |
| const int kNumTransactions = 2; |
| |
| for (int i = 0; i < kNumTransactions; ++i) { |
| context_list.push_back(std::make_unique<Context>()); |
| } |
| |
| // Let 1st transaction complete headers phase for ranges 40-49. |
| std::string first_read; |
| MockHttpRequest request1(transaction); |
| { |
| auto& c = context_list[0]; |
| c->result = cache.CreateTransaction(&c->trans); |
| ASSERT_THAT(c->result, IsOk()); |
| EXPECT_EQ(LOAD_STATE_IDLE, c->trans->GetLoadState()); |
| |
| c->result = |
| c->trans->Start(&request1, c->callback.callback(), NetLogWithSource()); |
| base::RunLoop().RunUntilIdle(); |
| |
| // Start writing to the cache so that MockDiskEntry::CouldBeSparse() returns |
| // true. |
| const int kBufferSize = 5; |
| scoped_refptr<IOBuffer> buffer = |
| base::MakeRefCounted<IOBuffer>(kBufferSize); |
| ReleaseBufferCompletionCallback cb(buffer.get()); |
| c->result = c->trans->Read(buffer.get(), kBufferSize, cb.callback()); |
| EXPECT_EQ(kBufferSize, cb.GetResult(c->result)); |
| |
| std::string data_read(buffer->data(), kBufferSize); |
| first_read = data_read; |
| |
| EXPECT_EQ(LOAD_STATE_READING_RESPONSE, c->trans->GetLoadState()); |
| } |
| |
| // 2nd transaction requests ranges 30-39. |
| transaction.request_headers = "Range: bytes = 30-39\r\n" EXTRA_HEADER; |
| MockHttpRequest request2(transaction); |
| { |
| auto& c = context_list[1]; |
| c->result = cache.CreateTransaction(&c->trans); |
| ASSERT_THAT(c->result, IsOk()); |
| EXPECT_EQ(LOAD_STATE_IDLE, c->trans->GetLoadState()); |
| |
| c->result = |
| c->trans->Start(&request2, c->callback.callback(), NetLogWithSource()); |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_EQ(LOAD_STATE_IDLE, c->trans->GetLoadState()); |
| } |
| |
| EXPECT_TRUE(cache.IsWriterPresent(kRangeGET_TransactionOK.url)); |
| EXPECT_EQ(1, cache.GetCountDoneHeadersQueue(kRangeGET_TransactionOK.url)); |
| |
| EXPECT_EQ(2, cache.network_layer()->transaction_count()); |
| EXPECT_EQ(0, cache.disk_cache()->open_count()); |
| EXPECT_EQ(1, cache.disk_cache()->create_count()); |
| |
| for (int i = 0; i < kNumTransactions; ++i) { |
| auto& c = context_list[i]; |
| if (c->result == ERR_IO_PENDING) |
| c->result = c->callback.WaitForResult(); |
| |
| if (i == 0) { |
| ReadRemainingAndVerifyTransaction(c->trans.get(), first_read, |
| transaction); |
| continue; |
| } |
| |
| transaction.data = "rg: 30-39 "; |
| ReadAndVerifyTransaction(c->trans.get(), transaction); |
| } |
| |
| EXPECT_EQ(2, cache.network_layer()->transaction_count()); |
| EXPECT_EQ(0, cache.disk_cache()->open_count()); |
| EXPECT_EQ(1, cache.disk_cache()->create_count()); |
| |
| // Fetch from the cache to check that ranges 30-49 have been successfully |
| // cached. |
| { |
| MockTransaction transaction(kRangeGET_TransactionOK); |
| transaction.request_headers = "Range: bytes = 30-49\r\n" EXTRA_HEADER; |
| transaction.data = "rg: 30-39 rg: 40-49 "; |
| std::string headers; |
| RunTransactionTestWithResponse(cache.http_cache(), transaction, &headers); |
| Verify206Response(headers, 30, 49); |
| } |
| |
| EXPECT_EQ(2, cache.network_layer()->transaction_count()); |
| EXPECT_EQ(1, cache.disk_cache()->open_count()); |
| EXPECT_EQ(1, cache.disk_cache()->create_count()); |
| |
| histograms.ExpectBucketCount( |
| histogram_name, |
| static_cast<int>(HttpCache::PARALLEL_WRITING_NOT_JOIN_RANGE), 1); |
| histograms.ExpectBucketCount( |
| histogram_name, static_cast<int>(HttpCache::PARALLEL_WRITING_CREATE), 2); |
| } |
| |
| // Tests that a request does not create Writers when readers is not empty. |
| TEST_F(HttpCacheTest, RangeGET_DoNotCreateWritersWhenReaderExists) { |
| MockHttpCache cache; |
| |
| // Save a request in the cache so that the next request can become a |
| // reader. |
| MockTransaction transaction(kRangeGET_Transaction); |
| transaction.request_headers = EXTRA_HEADER; |
| AddMockTransaction(&transaction); |
| RunTransactionTest(cache.http_cache(), transaction); |
| |
| // Let this request be a reader since it doesn't need validation as per its |
| // load flag. |
| transaction.load_flags |= LOAD_SKIP_CACHE_VALIDATION; |
| MockHttpRequest request(transaction); |
| Context context; |
| context.result = cache.CreateTransaction(&context.trans); |
| ASSERT_THAT(context.result, IsOk()); |
| context.result = context.trans->Start(&request, context.callback.callback(), |
| NetLogWithSource()); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_EQ(1, cache.GetCountReaders(transaction.url)); |
| RemoveMockTransaction(&transaction); |
| |
| // A range request should now "not" create Writers while readers is still |
| // non-empty. |
| MockTransaction range_transaction(kRangeGET_Transaction); |
| range_transaction.request_headers = "Range: bytes = 0-9\r\n" EXTRA_HEADER; |
| AddMockTransaction(&range_transaction); |
| MockHttpRequest range_request(range_transaction); |
| Context range_context; |
| range_context.result = cache.CreateTransaction(&range_context.trans); |
| ASSERT_THAT(range_context.result, IsOk()); |
| range_context.result = range_context.trans->Start( |
| &range_request, range_context.callback.callback(), NetLogWithSource()); |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_EQ(1, cache.GetCountReaders(transaction.url)); |
| EXPECT_FALSE(cache.IsWriterPresent(transaction.url)); |
| EXPECT_EQ(1, cache.GetCountDoneHeadersQueue(transaction.url)); |
| |
| RemoveMockTransaction(&range_transaction); |
| } |
| |
| // Tests parallel validation on range requests can be successfully restarted |
| // when there is a cache lock timeout. |
| TEST_F(HttpCacheTest, RangeGET_ParallelValidationCacheLockTimeout) { |
| MockHttpCache cache; |
| |
| ScopedMockTransaction transaction(kRangeGET_TransactionOK); |
| |
| std::vector<std::unique_ptr<Context>> context_list; |
| const int kNumTransactions = 2; |
| |
| for (int i = 0; i < kNumTransactions; ++i) { |
| context_list.push_back(std::make_unique<Context>()); |
| } |
| |
| // Let 1st transaction complete headers phase for ranges 40-49. |
| std::string first_read; |
| MockHttpRequest request1(transaction); |
| { |
| auto& c = context_list[0]; |
| c->result = cache.CreateTransaction(&c->trans); |
| ASSERT_THAT(c->result, IsOk()); |
| EXPECT_EQ(LOAD_STATE_IDLE, c->trans->GetLoadState()); |
| |
| c->result = |
| c->trans->Start(&request1, c->callback.callback(), NetLogWithSource()); |
| base::RunLoop().RunUntilIdle(); |
| |
| // Start writing to the cache so that MockDiskEntry::CouldBeSparse() returns |
| // true. |
| const int kBufferSize = 5; |
| scoped_refptr<IOBuffer> buffer = |
| base::MakeRefCounted<IOBuffer>(kBufferSize); |
| ReleaseBufferCompletionCallback cb(buffer.get()); |
| c->result = c->trans->Read(buffer.get(), kBufferSize, cb.callback()); |
| EXPECT_EQ(kBufferSize, cb.GetResult(c->result)); |
| |
| std::string data_read(buffer->data(), kBufferSize); |
| first_read = data_read; |
| |
| EXPECT_EQ(LOAD_STATE_READING_RESPONSE, c->trans->GetLoadState()); |
| } |
| |
| // Cache lock timeout will lead to dooming the entry since the transaction may |
| // have already written the headers. |
| cache.SimulateCacheLockTimeoutAfterHeaders(); |
| |
| // 2nd transaction requests ranges 30-39. |
| transaction.request_headers = "Range: bytes = 30-39\r\n" EXTRA_HEADER; |
| MockHttpRequest request2(transaction); |
| { |
| auto& c = context_list[1]; |
| c->result = cache.CreateTransaction(&c->trans); |
| ASSERT_THAT(c->result, IsOk()); |
| EXPECT_EQ(LOAD_STATE_IDLE, c->trans->GetLoadState()); |
| |
| c->result = |
| c->trans->Start(&request2, c->callback.callback(), NetLogWithSource()); |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_EQ(LOAD_STATE_IDLE, c->trans->GetLoadState()); |
| } |
| |
| EXPECT_EQ(0, cache.GetCountDoneHeadersQueue(kRangeGET_TransactionOK.url)); |
| |
| EXPECT_EQ(3, cache.network_layer()->transaction_count()); |
| EXPECT_EQ(0, cache.disk_cache()->open_count()); |
| EXPECT_EQ(1, cache.disk_cache()->create_count()); |
| |
| for (int i = 0; i < kNumTransactions; ++i) { |
| auto& c = context_list[i]; |
| if (c->result == ERR_IO_PENDING) |
| c->result = c->callback.WaitForResult(); |
| |
| if (i == 0) { |
| ReadRemainingAndVerifyTransaction(c->trans.get(), first_read, |
| transaction); |
| continue; |
| } |
| |
| transaction.data = "rg: 30-39 "; |
| ReadAndVerifyTransaction(c->trans.get(), transaction); |
| } |
| |
| EXPECT_EQ(3, cache.network_layer()->transaction_count()); |
| EXPECT_EQ(0, cache.disk_cache()->open_count()); |
| EXPECT_EQ(1, cache.disk_cache()->create_count()); |
| } |
| |
| // Tests a full request and a simultaneous range request and the range request |
| // dooms the entry created by the full request due to not being able to |
| // conditionalize. |
| TEST_F(HttpCacheTest, RangeGET_ParallelValidationCouldntConditionalize) { |
| MockHttpCache cache; |
| |
| MockTransaction mock_transaction(kSimpleGET_Transaction); |
| mock_transaction.url = kRangeGET_TransactionOK.url; |
| ScopedMockTransaction transaction(mock_transaction); |
| |
| // Remove the cache-control and other headers so that the response cannot be |
| // conditionalized. |
| transaction.response_headers = ""; |
| |
| std::vector<std::unique_ptr<Context>> context_list; |
| const int kNumTransactions = 2; |
| |
| for (int i = 0; i < kNumTransactions; ++i) { |
| context_list.push_back(std::make_unique<Context>()); |
| } |
| |
| // Let 1st transaction complete headers phase for no range and read some part |
| // of the response and write in the cache. |
| std::string first_read; |
| MockHttpRequest request1(transaction); |
| { |
| request1.url = GURL(kRangeGET_TransactionOK.url); |
| auto& c = context_list[0]; |
| c->result = cache.CreateTransaction(&c->trans); |
| ASSERT_THAT(c->result, IsOk()); |
| EXPECT_EQ(LOAD_STATE_IDLE, c->trans->GetLoadState()); |
| |
| c->result = |
| c->trans->Start(&request1, c->callback.callback(), NetLogWithSource()); |
| base::RunLoop().RunUntilIdle(); |
| |
| const int kBufferSize = 5; |
| scoped_refptr<IOBuffer> buffer = |
| base::MakeRefCounted<IOBuffer>(kBufferSize); |
| ReleaseBufferCompletionCallback cb(buffer.get()); |
| c->result = c->trans->Read(buffer.get(), kBufferSize, cb.callback()); |
| EXPECT_EQ(kBufferSize, cb.GetResult(c->result)); |
| |
| std::string data_read(buffer->data(), kBufferSize); |
| first_read = data_read; |
| |
| EXPECT_EQ(LOAD_STATE_READING_RESPONSE, c->trans->GetLoadState()); |
| } |
| |
| // 2nd transaction requests a range. |
| ScopedMockTransaction range_transaction(kRangeGET_TransactionOK); |
| range_transaction.request_headers = "Range: bytes = 0-29\r\n" EXTRA_HEADER; |
| MockHttpRequest request2(range_transaction); |
| { |
| auto& c = context_list[1]; |
| c->result = cache.CreateTransaction(&c->trans); |
| ASSERT_THAT(c->result, IsOk()); |
| EXPECT_EQ(LOAD_STATE_IDLE, c->trans->GetLoadState()); |
| |
| c->result = |
| c->trans->Start(&request2, c->callback.callback(), NetLogWithSource()); |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_EQ(LOAD_STATE_IDLE, c->trans->GetLoadState()); |
| } |
| |
| // The second request would have doomed the 1st entry and created a new entry. |
| EXPECT_EQ(2, cache.network_layer()->transaction_count()); |
| EXPECT_EQ(0, cache.disk_cache()->open_count()); |
| EXPECT_EQ(2, cache.disk_cache()->create_count()); |
| |
| for (int i = 0; i < kNumTransactions; ++i) { |
| auto& c = context_list[i]; |
| if (c->result == ERR_IO_PENDING) |
| c->result = c->callback.WaitForResult(); |
| |
| if (i == 0) { |
| ReadRemainingAndVerifyTransaction(c->trans.get(), first_read, |
| transaction); |
| continue; |
| } |
| range_transaction.data = "rg: 00-09 rg: 10-19 rg: 20-29 "; |
| ReadAndVerifyTransaction(c->trans.get(), range_transaction); |
| } |
| } |
| |
| // Tests a 200 request and a simultaneous range request where conditionalization |
| // is possible. |
| TEST_F(HttpCacheTest, RangeGET_ParallelValidationCouldConditionalize) { |
| MockHttpCache cache; |
| |
| MockTransaction mock_transaction(kSimpleGET_Transaction); |
| mock_transaction.url = kRangeGET_TransactionOK.url; |
| mock_transaction.data = kFullRangeData; |
| std::string response_headers_str = base::StrCat( |
| {"ETag: StrongOne\n", "Content-Length:", |
| base::IntToString(strlen(kFullRangeData)), "\n"}); |
| mock_transaction.response_headers = response_headers_str.c_str(); |
| |
| ScopedMockTransaction transaction(mock_transaction); |
| |
| std::vector<std::unique_ptr<Context>> context_list; |
| const int kNumTransactions = 2; |
| |
| for (int i = 0; i < kNumTransactions; ++i) { |
| context_list.push_back(std::make_unique<Context>()); |
| } |
| |
| // Let 1st transaction complete headers phase for no range and read some part |
| // of the response and write in the cache. |
| std::string first_read; |
| MockHttpRequest request1(transaction); |
| { |
| request1.url = GURL(kRangeGET_TransactionOK.url); |
| auto& c = context_list[0]; |
| c->result = cache.CreateTransaction(&c->trans); |
| ASSERT_THAT(c->result, IsOk()); |
| EXPECT_EQ(LOAD_STATE_IDLE, c->trans->GetLoadState()); |
| |
| c->result = |
| c->trans->Start(&request1, c->callback.callback(), NetLogWithSource()); |
| base::RunLoop().RunUntilIdle(); |
| |
| const int kBufferSize = 5; |
| scoped_refptr<IOBuffer> buffer = |
| base::MakeRefCounted<IOBuffer>(kBufferSize); |
| ReleaseBufferCompletionCallback cb(buffer.get()); |
| c->result = c->trans->Read(buffer.get(), kBufferSize, cb.callback()); |
| EXPECT_EQ(kBufferSize, cb.GetResult(c->result)); |
| |
| std::string data_read(buffer->data(), kBufferSize); |
| first_read = data_read; |
| |
| EXPECT_EQ(LOAD_STATE_READING_RESPONSE, c->trans->GetLoadState()); |
| } |
| |
| // 2nd transaction requests a range. |
| ScopedMockTransaction range_transaction(kRangeGET_TransactionOK); |
| range_transaction.request_headers = "Range: bytes = 0-29\r\n" EXTRA_HEADER; |
| MockHttpRequest request2(range_transaction); |
| { |
| auto& c = context_list[1]; |
| c->result = cache.CreateTransaction(&c->trans); |
| ASSERT_THAT(c->result, IsOk()); |
| EXPECT_EQ(LOAD_STATE_IDLE, c->trans->GetLoadState()); |
| |
| c->result = |
| c->trans->Start(&request2, c->callback.callback(), NetLogWithSource()); |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_EQ(LOAD_STATE_IDLE, c->trans->GetLoadState()); |
| } |
| |
| EXPECT_EQ(2, cache.network_layer()->transaction_count()); |
| EXPECT_EQ(0, cache.disk_cache()->open_count()); |
| EXPECT_EQ(1, cache.disk_cache()->create_count()); |
| |
| // Finish and verify the first request. |
| auto& c0 = context_list[0]; |
| c0->result = c0->callback.WaitForResult(); |
| ReadRemainingAndVerifyTransaction(c0->trans.get(), first_read, transaction); |
| |
| // And the second. |
| auto& c1 = context_list[1]; |
| c1->result = c1->callback.WaitForResult(); |
| |
| range_transaction.data = "rg: 00-09 rg: 10-19 rg: 20-29 "; |
| ReadAndVerifyTransaction(c1->trans.get(), range_transaction); |
| } |
| |
| // Tests parallel validation on range requests with overlapping ranges. |
| TEST_F(HttpCacheTest, RangeGET_ParallelValidationOverlappingRanges) { |
| MockHttpCache cache; |
| |
| ScopedMockTransaction transaction(kRangeGET_TransactionOK); |
| |
| std::vector<std::unique_ptr<Context>> context_list; |
| const int kNumTransactions = 2; |
| |
| for (int i = 0; i < kNumTransactions; ++i) { |
| context_list.push_back(std::make_unique<Context>()); |
| } |
| |
| // Let 1st transaction complete headers phase for ranges 40-49. |
| std::string first_read; |
| MockHttpRequest request1(transaction); |
| { |
| auto& c = context_list[0]; |
| c->result = cache.CreateTransaction(&c->trans); |
| ASSERT_THAT(c->result, IsOk()); |
| EXPECT_EQ(LOAD_STATE_IDLE, c->trans->GetLoadState()); |
| |
| c->result = |
| c->trans->Start(&request1, c->callback.callback(), NetLogWithSource()); |
| base::RunLoop().RunUntilIdle(); |
| |
| // Start writing to the cache so that MockDiskEntry::CouldBeSparse() returns |
| // true. |
| const int kBufferSize = 5; |
| scoped_refptr<IOBuffer> buffer = |
| base::MakeRefCounted<IOBuffer>(kBufferSize); |
| ReleaseBufferCompletionCallback cb(buffer.get()); |
| c->result = c->trans->Read(buffer.get(), kBufferSize, cb.callback()); |
| EXPECT_EQ(kBufferSize, cb.GetResult(c->result)); |
| |
| std::string data_read(buffer->data(), kBufferSize); |
| first_read = data_read; |
| |
| EXPECT_EQ(LOAD_STATE_READING_RESPONSE, c->trans->GetLoadState()); |
| } |
| |
| // 2nd transaction requests ranges 30-49. |
| transaction.request_headers = "Range: bytes = 30-49\r\n" EXTRA_HEADER; |
| MockHttpRequest request2(transaction); |
| { |
| auto& c = context_list[1]; |
| c->result = cache.CreateTransaction(&c->trans); |
| ASSERT_THAT(c->result, IsOk()); |
| EXPECT_EQ(LOAD_STATE_IDLE, c->trans->GetLoadState()); |
| |
| c->result = |
| c->trans->Start(&request2, c->callback.callback(), NetLogWithSource()); |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_EQ(LOAD_STATE_IDLE, c->trans->GetLoadState()); |
| } |
| |
| EXPECT_TRUE(cache.IsWriterPresent(kRangeGET_TransactionOK.url)); |
| EXPECT_EQ(1, cache.GetCountDoneHeadersQueue(kRangeGET_TransactionOK.url)); |
| |
| // Should have created another transaction for the uncached range. |
| EXPECT_EQ(2, cache.network_layer()->transaction_count()); |
| EXPECT_EQ(0, cache.disk_cache()->open_count()); |
| EXPECT_EQ(1, cache.disk_cache()->create_count()); |
| |
| for (int i = 0; i < kNumTransactions; ++i) { |
| auto& c = context_list[i]; |
| if (c->result == ERR_IO_PENDING) |
| c->result = c->callback.WaitForResult(); |
| |
| if (i == 0) { |
| ReadRemainingAndVerifyTransaction(c->trans.get(), first_read, |
| transaction); |
| continue; |
| } |
| |
| transaction.data = "rg: 30-39 rg: 40-49 "; |
| ReadAndVerifyTransaction(c->trans.get(), transaction); |
| } |
| |
| EXPECT_EQ(2, cache.network_layer()->transaction_count()); |
| EXPECT_EQ(0, cache.disk_cache()->open_count()); |
| EXPECT_EQ(1, cache.disk_cache()->create_count()); |
| |
| // Fetch from the cache to check that ranges 30-49 have been successfully |
| // cached. |
| { |
| MockTransaction transaction(kRangeGET_TransactionOK); |
| transaction.request_headers = "Range: bytes = 30-49\r\n" EXTRA_HEADER; |
| transaction.data = "rg: 30-39 rg: 40-49 "; |
| std::string headers; |
| RunTransactionTestWithResponse(cache.http_cache(), transaction, &headers); |
| Verify206Response(headers, 30, 49); |
| } |
| |
| EXPECT_EQ(2, cache.network_layer()->transaction_count()); |
| EXPECT_EQ(0, cache.disk_cache()->open_count()); |
| EXPECT_EQ(1, cache.disk_cache()->create_count()); |
| } |
| |
| // Tests parallel validation on range requests with overlapping ranges and the |
| // impact of deleting the writer on transactions that have validated. |
| TEST_F(HttpCacheTest, RangeGET_ParallelValidationRestartDoneHeaders) { |
| MockHttpCache cache; |
| |
| ScopedMockTransaction transaction(kRangeGET_TransactionOK); |
| |
| std::vector<std::unique_ptr<Context>> context_list; |
| const int kNumTransactions = 2; |
| |
| for (int i = 0; i < kNumTransactions; ++i) { |
| context_list.push_back(std::make_unique<Context>()); |
| } |
| |
| // Let 1st transaction complete headers phase for ranges 40-59. |
| std::string first_read; |
| transaction.request_headers = "Range: bytes = 40-59\r\n" EXTRA_HEADER; |
| MockHttpRequest request1(transaction); |
| { |
| auto& c = context_list[0]; |
| c->result = cache.CreateTransaction(&c->trans); |
| ASSERT_THAT(c->result, IsOk()); |
| EXPECT_EQ(LOAD_STATE_IDLE, c->trans->GetLoadState()); |
| |
| c->result = |
| c->trans->Start(&request1, c->callback.callback(), NetLogWithSource()); |
| base::RunLoop().RunUntilIdle(); |
| |
| // Start writing to the cache so that MockDiskEntry::CouldBeSparse() returns |
| // true. |
| const int kBufferSize = 10; |
| scoped_refptr<IOBuffer> buffer = |
| base::MakeRefCounted<IOBuffer>(kBufferSize); |
| ReleaseBufferCompletionCallback cb(buffer.get()); |
| c->result = c->trans->Read(buffer.get(), kBufferSize, cb.callback()); |
| EXPECT_EQ(kBufferSize, cb.GetResult(c->result)); |
| |
| std::string data_read(buffer->data(), kBufferSize); |
| first_read = data_read; |
| |
| EXPECT_EQ(LOAD_STATE_READING_RESPONSE, c->trans->GetLoadState()); |
| } |
| |
| // 2nd transaction requests ranges 30-59. |
| transaction.request_headers = "Range: bytes = 30-59\r\n" EXTRA_HEADER; |
| MockHttpRequest request2(transaction); |
| { |
| auto& c = context_list[1]; |
| c->result = cache.CreateTransaction(&c->trans); |
| ASSERT_THAT(c->result, IsOk()); |
| EXPECT_EQ(LOAD_STATE_IDLE, c->trans->GetLoadState()); |
| |
| c->result = |
| c->trans->Start(&request2, c->callback.callback(), NetLogWithSource()); |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_EQ(LOAD_STATE_IDLE, c->trans->GetLoadState()); |
| } |
| |
| EXPECT_TRUE(cache.IsWriterPresent(kRangeGET_TransactionOK.url)); |
| EXPECT_EQ(1, cache.GetCountDoneHeadersQueue(kRangeGET_TransactionOK.url)); |
| |
| EXPECT_EQ(2, cache.network_layer()->transaction_count()); |
| EXPECT_EQ(0, cache.disk_cache()->open_count()); |
| EXPECT_EQ(1, cache.disk_cache()->create_count()); |
| |
| // Delete the writer transaction. |
| context_list[0].reset(); |
| |
| base::RunLoop().RunUntilIdle(); |
| |
| transaction.data = "rg: 30-39 rg: 40-49 rg: 50-59 "; |
| ReadAndVerifyTransaction(context_list[1]->trans.get(), transaction); |
| |
| // Create another network transaction since the 2nd transaction is restarted. |
| // 30-39 will be read from network, 40-49 from the cache and 50-59 from the |
| // network. |
| EXPECT_EQ(4, cache.network_layer()->transaction_count()); |
| EXPECT_EQ(0, cache.disk_cache()->open_count()); |
| EXPECT_EQ(1, cache.disk_cache()->create_count()); |
| |
| // Fetch from the cache to check that ranges 30-49 have been successfully |
| // cached. |
| { |
| MockTransaction transaction(kRangeGET_TransactionOK); |
| transaction.request_headers = "Range: bytes = 30-49\r\n" EXTRA_HEADER; |
| transaction.data = "rg: 30-39 rg: 40-49 "; |
| std::string headers; |
| RunTransactionTestWithResponse(cache.http_cache(), transaction, &headers); |
| Verify206Response(headers, 30, 49); |
| } |
| |
| EXPECT_EQ(4, cache.network_layer()->transaction_count()); |
| EXPECT_EQ(1, cache.disk_cache()->open_count()); |
| EXPECT_EQ(1, cache.disk_cache()->create_count()); |
| } |
| |
| // Parallel validation results in 200. |
| TEST_F(HttpCacheTest, SimpleGET_ParallelValidationNoMatch) { |
| MockHttpCache cache; |
| MockHttpRequest request(kSimpleGET_Transaction); |
| request.load_flags |= LOAD_VALIDATE_CACHE; |
| std::vector<std::unique_ptr<Context>> context_list; |
| const int kNumTransactions = 5; |
| for (int i = 0; i < kNumTransactions; ++i) { |
| context_list.push_back(std::make_unique<Context>()); |
| auto& c = context_list[i]; |
| c->result = cache.CreateTransaction(&c->trans); |
| ASSERT_THAT(c->result, IsOk()); |
| EXPECT_EQ(LOAD_STATE_IDLE, c->trans->GetLoadState()); |
| c->result = |
| c->trans->Start(&request, c->callback.callback(), NetLogWithSource()); |
| } |
| |
| // All requests are waiting for the active entry. |
| for (auto& context : context_list) { |
| EXPECT_EQ(LOAD_STATE_WAITING_FOR_CACHE, context->trans->GetLoadState()); |
| } |
| |
| // Allow all requests to move from the Create queue to the active entry. |
| base::RunLoop().RunUntilIdle(); |
| |
| // The first request should be a writer at this point, and the subsequent |
| // requests should have passed the validation phase and created their own |
| // entries since none of them matched the headers of the earlier one. |
| EXPECT_TRUE(cache.IsWriterPresent(kSimpleGET_Transaction.url)); |
| |
| EXPECT_EQ(5, cache.network_layer()->transaction_count()); |
| EXPECT_EQ(0, cache.disk_cache()->open_count()); |
| EXPECT_EQ(5, cache.disk_cache()->create_count()); |
| |
| // All requests depend on the writer, and the writer is between Start and |
| // Read, i.e. idle. |
| for (auto& context : context_list) { |
| EXPECT_EQ(LOAD_STATE_IDLE, context->trans->GetLoadState()); |
| } |
| |
| for (auto& context : context_list) { |
| if (context->result == ERR_IO_PENDING) |
| context->result = context->callback.WaitForResult(); |
| ReadAndVerifyTransaction(context->trans.get(), kSimpleGET_Transaction); |
| } |
| |
| EXPECT_EQ(5, cache.network_layer()->transaction_count()); |
| EXPECT_EQ(0, cache.disk_cache()->open_count()); |
| EXPECT_EQ(5, cache.disk_cache()->create_count()); |
| } |
| |
| // Parallel validation results in 200 for 1 transaction and validation matches |
| // for subsequent transactions. |
| TEST_F(HttpCacheTest, SimpleGET_ParallelValidationNoMatch1) { |
| MockHttpCache cache; |
| MockHttpRequest request(kSimpleGET_Transaction); |
| |
| MockTransaction transaction(kSimpleGET_Transaction); |
| transaction.load_flags |= LOAD_VALIDATE_CACHE; |
| MockHttpRequest validate_request(transaction); |
| std::vector<std::unique_ptr<Context>> context_list; |
| const int kNumTransactions = 5; |
| for (int i = 0; i < kNumTransactions; ++i) { |
| context_list.push_back(std::make_unique<Context>()); |
| auto& c = context_list[i]; |
| c->result = cache.CreateTransaction(&c->trans); |
| ASSERT_THAT(c->result, IsOk()); |
| EXPECT_EQ(LOAD_STATE_IDLE, c->trans->GetLoadState()); |
| |
| MockHttpRequest* this_request = &request; |
| if (i == 1) |
| this_request = &validate_request; |
| |
| c->result = c->trans->Start(this_request, c->callback.callback(), |
| NetLogWithSource()); |
| } |
| |
| // All requests are waiting for the active entry. |
| for (auto& context : context_list) { |
| EXPECT_EQ(LOAD_STATE_WAITING_FOR_CACHE, context->trans->GetLoadState()); |
| } |
| |
| // Allow all requests to move from the Create queue to the active entry. |
| base::RunLoop().RunUntilIdle(); |
| |
| // The new entry will have all the transactions except the first one which |
| // will continue in the doomed entry. |
| EXPECT_EQ(kNumTransactions - 1, |
| cache.GetCountWriterTransactions(kSimpleGET_Transaction.url)); |
| |
| EXPECT_EQ(1, cache.disk_cache()->doomed_count()); |
| |
| EXPECT_EQ(2, cache.network_layer()->transaction_count()); |
| EXPECT_EQ(0, cache.disk_cache()->open_count()); |
| EXPECT_EQ(2, cache.disk_cache()->create_count()); |
| |
| for (auto& context : context_list) { |
| EXPECT_EQ(LOAD_STATE_IDLE, context->trans->GetLoadState()); |
| } |
| |
| for (size_t i = 0; i < context_list.size(); i++) { |
| if (context_list[i]->result == ERR_IO_PENDING) |
| context_list[i]->result = context_list[i]->callback.WaitForResult(); |
| |
| ReadAndVerifyTransaction(context_list[i]->trans.get(), |
| kSimpleGET_Transaction); |
| } |
| |
| EXPECT_EQ(2, cache.network_layer()->transaction_count()); |
| EXPECT_EQ(0, cache.disk_cache()->open_count()); |
| EXPECT_EQ(2, cache.disk_cache()->create_count()); |
| } |
| |
| // Tests that a GET followed by a DELETE results in DELETE immediately starting |
| // the headers phase and the entry is doomed. |
| TEST_F(HttpCacheTest, SimpleGET_ParallelValidationDelete) { |
| MockHttpCache cache; |
| |
| MockHttpRequest request(kSimpleGET_Transaction); |
| request.load_flags |= LOAD_VALIDATE_CACHE; |
| |
| MockHttpRequest delete_request(kSimpleGET_Transaction); |
| delete_request.method = "DELETE"; |
| |
| std::vector<std::unique_ptr<Context>> context_list; |
| const int kNumTransactions = 2; |
| |
| for (int i = 0; i < kNumTransactions; ++i) { |
| context_list.push_back(std::make_unique<Context>()); |
| auto& c = context_list[i]; |
| |
| MockHttpRequest* this_request = &request; |
| if (i == 1) |
| this_request = &delete_request; |
| |
| c->result = cache.CreateTransaction(&c->trans); |
| ASSERT_THAT(c->result, IsOk()); |
| EXPECT_EQ(LOAD_STATE_IDLE, c->trans->GetLoadState()); |
| |
| c->result = c->trans->Start(this_request, c->callback.callback(), |
| NetLogWithSource()); |
| } |
| |
| // All requests are waiting for the active entry. |
| for (auto& context : context_list) { |
| EXPECT_EQ(LOAD_STATE_WAITING_FOR_CACHE, context->trans->GetLoadState()); |
| } |
| |
| // Allow all requests to move from the Create queue to the active entry. |
| base::RunLoop().RunUntilIdle(); |
| |
| // The first request should be a writer at this point, and the subsequent |
| // request should have passed the validation phase and doomed the existing |
| // entry. |
| EXPECT_TRUE( |
| cache.disk_cache()->IsDiskEntryDoomed(kSimpleGET_Transaction.url)); |
| |
| EXPECT_EQ(2, cache.network_layer()->transaction_count()); |
| EXPECT_EQ(0, cache.disk_cache()->open_count()); |
| EXPECT_EQ(1, cache.disk_cache()->create_count()); |
| |
| // All requests depend on the writer, and the writer is between Start and |
| // Read, i.e. idle. |
| for (auto& context : context_list) { |
| EXPECT_EQ(LOAD_STATE_IDLE, context->trans->GetLoadState()); |
| } |
| |
| for (auto& context : context_list) { |
| if (context->result == ERR_IO_PENDING) |
| context->result = context->callback.WaitForResult(); |
| ReadAndVerifyTransaction(context->trans.get(), kSimpleGET_Transaction); |
| } |
| |
| EXPECT_EQ(2, cache.network_layer()->transaction_count()); |
| EXPECT_EQ(0, cache.disk_cache()->open_count()); |
| EXPECT_EQ(1, cache.disk_cache()->create_count()); |
| } |
| |
| // Tests that a transaction which is in validated queue can be destroyed without |
| // any impact to other transactions. |
| TEST_F(HttpCacheTest, SimpleGET_ParallelValidationCancelValidated) { |
| MockHttpCache cache; |
| |
| MockHttpRequest request(kSimpleGET_Transaction); |
| |
| MockTransaction transaction(kSimpleGET_Transaction); |
| transaction.load_flags |= LOAD_ONLY_FROM_CACHE; |
| MockHttpRequest read_only_request(transaction); |
| |
| std::vector<std::unique_ptr<Context>> context_list; |
| const int kNumTransactions = 2; |
| |
| for (int i = 0; i < kNumTransactions; ++i) { |
| context_list.push_back(std::make_unique<Context>()); |
| auto& c = context_list[i]; |
| |
| c->result = cache.CreateTransaction(&c->trans); |
| ASSERT_THAT(c->result, IsOk()); |
| |
| MockHttpRequest* current_request = i == 1 ? &read_only_request : &request; |
| |
| c->result = c->trans->Start(current_request, c->callback.callback(), |
| NetLogWithSource()); |
| } |
| |
| // Allow all requests to move from the Create queue to the active entry. |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_EQ(1, cache.network_layer()->transaction_count()); |
| EXPECT_EQ(0, cache.disk_cache()->open_count()); |
| EXPECT_EQ(1, cache.disk_cache()->create_count()); |
| |
| EXPECT_EQ(1, cache.GetCountWriterTransactions(kSimpleGET_Transaction.url)); |
| EXPECT_EQ(1, cache.GetCountDoneHeadersQueue(kSimpleGET_Transaction.url)); |
| |
| context_list[1].reset(); |
| |
| EXPECT_EQ(0, cache.GetCountDoneHeadersQueue(kSimpleGET_Transaction.url)); |
| |
| // Complete the rest of the transactions. |
| for (auto& context : context_list) { |
| if (!context) |
| continue; |
| ReadAndVerifyTransaction(context->trans.get(), kSimpleGET_Transaction); |
| } |
| |
| EXPECT_EQ(1, cache.network_layer()->transaction_count()); |
| EXPECT_EQ(0, cache.disk_cache()->open_count()); |
| EXPECT_EQ(1, cache.disk_cache()->create_count()); |
| } |
| |
| // Tests that an idle writer transaction can be deleted without impacting the |
| // existing writers. |
| TEST_F(HttpCacheTest, SimpleGET_ParallelWritingCancelIdleTransaction) { |
| MockHttpCache cache; |
| |
| MockHttpRequest request(kSimpleGET_Transaction); |
| |
| std::vector<std::unique_ptr<Context>> context_list; |
| const int kNumTransactions = 2; |
| |
| for (int i = 0; i < kNumTransactions; ++i) { |
| context_list.push_back(std::make_unique<Context>()); |
| auto& c = context_list[i]; |
| |
| c->result = cache.CreateTransaction(&c->trans); |
| ASSERT_THAT(c->result, IsOk()); |
| |
| c->result = |
| c->trans->Start(&request, c->callback.callback(), NetLogWithSource()); |
| } |
| |
| // Allow all requests to move from the Create queue to the active entry. |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_EQ(1, cache.network_layer()->transaction_count()); |
| EXPECT_EQ(0, cache.disk_cache()->open_count()); |
| EXPECT_EQ(1, cache.disk_cache()->create_count()); |
| |
| // Both transactions would be added to writers. |
| EXPECT_EQ(kNumTransactions, |
| cache.GetCountWriterTransactions(kSimpleGET_Transaction.url)); |
| |
| context_list[1].reset(); |
| |
| EXPECT_EQ(kNumTransactions - 1, |
| cache.GetCountWriterTransactions(kSimpleGET_Transaction.url)); |
| |
| // Complete the rest of the transactions. |
| for (auto& context : context_list) { |
| if (!context) |
| continue; |
| ReadAndVerifyTransaction(context->trans.get(), kSimpleGET_Transaction); |
| } |
| |
| EXPECT_EQ(1, cache.network_layer()->transaction_count()); |
| EXPECT_EQ(0, cache.disk_cache()->open_count()); |
| EXPECT_EQ(1, cache.disk_cache()->create_count()); |
| } |
| |
| // Tests that a transaction which is in validated queue can timeout and start |
| // the headers phase again. |
| TEST_F(HttpCacheTest, SimpleGET_ParallelValidationValidatedTimeout) { |
| MockHttpCache cache; |
| |
| MockHttpRequest request(kSimpleGET_Transaction); |
| |
| MockTransaction transaction(kSimpleGET_Transaction); |
| transaction.load_flags |= LOAD_ONLY_FROM_CACHE; |
| MockHttpRequest read_only_request(transaction); |
| |
| std::vector<std::unique_ptr<Context>> context_list; |
| const int kNumTransactions = 2; |
| |
| for (int i = 0; i < kNumTransactions; ++i) { |
| context_list.push_back(std::make_unique<Context>()); |
| auto& c = context_list[i]; |
| |
| MockHttpRequest* this_request = &request; |
| if (i == 1) { |
| this_request = &read_only_request; |
| cache.SimulateCacheLockTimeoutAfterHeaders(); |
| } |
| |
| c->result = cache.CreateTransaction(&c->trans); |
| ASSERT_THAT(c->result, IsOk()); |
| |
| c->result = c->trans->Start(this_request, c->callback.callback(), |
| NetLogWithSource()); |
| } |
| |
| // Allow all requests to move from the Create queue to the active entry. |
| base::RunLoop().RunUntilIdle(); |
| |
| // The first request should be a writer at this point, and the subsequent |
| // requests should have completed validation, timed out and restarted. |
| // Since it is a read only request, it will error out. |
| |
| EXPECT_EQ(1, cache.network_layer()->transaction_count()); |
| EXPECT_EQ(0, cache.disk_cache()->open_count()); |
| EXPECT_EQ(1, cache.disk_cache()->create_count()); |
| |
| EXPECT_TRUE(cache.IsWriterPresent(kSimpleGET_Transaction.url)); |
| EXPECT_EQ(0, cache.GetCountDoneHeadersQueue(kSimpleGET_Transaction.url)); |
| |
| base::RunLoop().RunUntilIdle(); |
| |
| int rv = context_list[1]->callback.WaitForResult(); |
| EXPECT_EQ(ERR_CACHE_MISS, rv); |
| |
| ReadAndVerifyTransaction(context_list[0]->trans.get(), |
| kSimpleGET_Transaction); |
| } |
| |
| // Tests that a transaction which is in readers can be destroyed without |
| // any impact to other transactions. |
| TEST_F(HttpCacheTest, SimpleGET_ParallelValidationCancelReader) { |
| MockHttpCache cache; |
| |
| MockHttpRequest request(kSimpleGET_Transaction); |
| |
| MockTransaction transaction(kSimpleGET_Transaction); |
| transaction.load_flags |= LOAD_VALIDATE_CACHE; |
| MockHttpRequest validate_request(transaction); |
| |
| int kNumTransactions = 4; |
| std::vector<std::unique_ptr<Context>> context_list; |
| |
| for (int i = 0; i < kNumTransactions; ++i) { |
| context_list.push_back(std::make_unique<Context>()); |
| auto& c = context_list[i]; |
| |
| c->result = cache.CreateTransaction(&c->trans); |
| ASSERT_THAT(c->result, IsOk()); |
| |
| MockHttpRequest* this_request = &request; |
| if (i == 3) { |
| this_request = &validate_request; |
| c->trans->SetBeforeNetworkStartCallback(base::Bind(&DeferCallback)); |
| } |
| |
| c->result = c->trans->Start(this_request, c->callback.callback(), |
| NetLogWithSource()); |
| } |
| |
| // Allow all requests to move from the Create queue to the active entry. |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_EQ(2, cache.network_layer()->transaction_count()); |
| EXPECT_EQ(0, cache.disk_cache()->open_count()); |
| EXPECT_EQ(1, cache.disk_cache()->create_count()); |
| |
| EXPECT_EQ(kNumTransactions - 1, |
| cache.GetCountWriterTransactions(kSimpleGET_Transaction.url)); |
| EXPECT_TRUE(cache.IsHeadersTransactionPresent(kSimpleGET_Transaction.url)); |
| |
| // Complete the response body. |
| auto& c = context_list[0]; |
| ReadAndVerifyTransaction(c->trans.get(), kSimpleGET_Transaction); |
| |
| // Rest of the transactions should move to readers. |
| EXPECT_FALSE(cache.IsWriterPresent(kSimpleGET_Transaction.url)); |
| EXPECT_EQ(kNumTransactions - 2, |
| cache.GetCountReaders(kSimpleGET_Transaction.url)); |
| EXPECT_EQ(0, cache.GetCountDoneHeadersQueue(kSimpleGET_Transaction.url)); |
| EXPECT_TRUE(cache.IsHeadersTransactionPresent(kSimpleGET_Transaction.url)); |
| |
| // Add 2 new transactions. |
| kNumTransactions = 6; |
| |
| for (int i = 4; i < kNumTransactions; ++i) { |
| context_list.push_back(std::make_unique<Context>()); |
| auto& c = context_list[i]; |
| |
| c->result = cache.CreateTransaction(&c->trans); |
| ASSERT_THAT(c->result, IsOk()); |
| |
| c->result = |
| c->trans->Start(&request, c->callback.callback(), NetLogWithSource()); |
| } |
| |
| EXPECT_EQ(2, cache.GetCountAddToEntryQueue(kSimpleGET_Transaction.url)); |
| |
| // Delete a reader. |
| context_list[1].reset(); |
| |
| // Deleting the reader did not impact any other transaction. |
| EXPECT_EQ(1, cache.GetCountReaders(kSimpleGET_Transaction.url)); |
| EXPECT_EQ(2, cache.GetCountAddToEntryQueue(kSimpleGET_Transaction.url)); |
| EXPECT_TRUE(cache.IsHeadersTransactionPresent(kSimpleGET_Transaction.url)); |
| |
| // Resume network start for headers_transaction. It will doom the entry as it |
| // will be a 200 and will go to network for the response body. |
| auto& context = context_list[3]; |
| context->trans->ResumeNetworkStart(); |
| |
| // The pending transactions will be added to a new entry as writers. |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_EQ(3, cache.GetCountWriterTransactions(kSimpleGET_Transaction.url)); |
| |
| // Complete the rest of the transactions. |
| for (int i = 2; i < kNumTransactions; ++i) { |
| auto& c = context_list[i]; |
| ReadAndVerifyTransaction(c->trans.get(), kSimpleGET_Transaction); |
| } |
| |
| EXPECT_EQ(2, cache.network_layer()->transaction_count()); |
| EXPECT_EQ(0, cache.disk_cache()->open_count()); |
| EXPECT_EQ(2, cache.disk_cache()->create_count()); |
| } |
| |
| // Tests that when the only writer goes away, it immediately cleans up rather |
| // than wait for the network request to finish. See https://crbug.com/804868. |
| TEST_F(HttpCacheTest, SimpleGET_HangingCacheWriteCleanup) { |
| MockHttpCache mock_cache; |
| MockHttpRequest request(kSimpleGET_Transaction); |
| |
| std::unique_ptr<HttpTransaction> transaction; |
| mock_cache.CreateTransaction(&transaction); |
| TestCompletionCallback callback; |
| int result = |
| transaction->Start(&request, callback.callback(), NetLogWithSource()); |
| |
| // Get the transaction ready to read. |
| result = callback.GetResult(result); |
| |
| // Read the first byte. |
| scoped_refptr<IOBuffer> buffer = base::MakeRefCounted<IOBuffer>(1); |
| ReleaseBufferCompletionCallback buffer_callback(buffer.get()); |
| result = transaction->Read(buffer.get(), 1, buffer_callback.callback()); |
| EXPECT_EQ(1, buffer_callback.GetResult(result)); |
| |
| // Read the second byte, but leave the cache write hanging. |
| scoped_refptr<MockDiskEntry> entry = |
| mock_cache.disk_cache()->GetDiskEntryRef(kSimpleGET_Transaction.url); |
| entry->SetDefer(MockDiskEntry::DEFER_WRITE); |
| |
| buffer = base::MakeRefCounted<IOBuffer>(1); |
| ReleaseBufferCompletionCallback buffer_callback2(buffer.get()); |
| result = transaction->Read(buffer.get(), 1, buffer_callback2.callback()); |
| EXPECT_EQ(ERR_IO_PENDING, result); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(mock_cache.IsWriterPresent(kSimpleGET_Transaction.url)); |
| |
| // At this point the next byte should have been read from the network but is |
| // waiting to be written to the cache. Destroy the transaction and make sure |
| // that everything has been cleaned up. |
| transaction = nullptr; |
| EXPECT_FALSE(mock_cache.IsWriterPresent(kSimpleGET_Transaction.url)); |
| EXPECT_FALSE(mock_cache.network_layer()->last_transaction()); |
| } |
| |
| // Tests that a transaction writer can be destroyed mid-read. |
| // A waiting for read transaction should be able to read the data that was |
| // driven by the Read started by the cancelled writer. |
| TEST_F(HttpCacheTest, SimpleGET_ParallelWritingCancelWriter) { |
| MockHttpCache cache; |
| |
| MockHttpRequest request(kSimpleGET_Transaction); |
| |
| MockTransaction transaction(kSimpleGET_Transaction); |
| transaction.load_flags |= LOAD_VALIDATE_CACHE; |
| MockHttpRequest validate_request(transaction); |
| |
| const int kNumTransactions = 3; |
| std::vector<std::unique_ptr<Context>> context_list; |
| |
| for (int i = 0; i < kNumTransactions; ++i) { |
| context_list.push_back(std::make_unique<Context>()); |
| auto& c = context_list[i]; |
| |
| c->result = cache.CreateTransaction(&c->trans); |
| ASSERT_THAT(c->result, IsOk()); |
| |
| MockHttpRequest* this_request = &request; |
| if (i == 2) { |
| this_request = &validate_request; |
| c->trans->SetBeforeNetworkStartCallback(base::Bind(&DeferCallback)); |
| } |
| |
| c->result = c->trans->Start(this_request, c->callback.callback(), |
| NetLogWithSource()); |
| } |
| |
| // Allow all requests to move from the Create queue to the active entry. |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_EQ(2, cache.network_layer()->transaction_count()); |
| EXPECT_EQ(0, cache.disk_cache()->open_count()); |
| EXPECT_EQ(1, cache.disk_cache()->create_count()); |
| |
| EXPECT_TRUE(cache.IsHeadersTransactionPresent(kSimpleGET_Transaction.url)); |
| EXPECT_EQ(2, cache.GetCountWriterTransactions(kSimpleGET_Transaction.url)); |
| |
| // Initiate Read from both writers and kill 1 of them mid-read. |
| std::string first_read; |
| for (int i = 0; i < 2; i++) { |
| auto& c = context_list[i]; |
| const int kBufferSize = 5; |
| scoped_refptr<IOBuffer> buffer = |
| base::MakeRefCounted<IOBuffer>(kBufferSize); |
| ReleaseBufferCompletionCallback cb(buffer.get()); |
| c->result = c->trans->Read(buffer.get(), kBufferSize, cb.callback()); |
| EXPECT_EQ(ERR_IO_PENDING, c->result); |
| // Deleting one writer at this point will not impact other transactions |
| // since writers contain more transactions. |
| if (i == 1) { |
| context_list[0].reset(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_EQ(kBufferSize, cb.GetResult(c->result)); |
| std::string data_read(buffer->data(), kBufferSize); |
| first_read = data_read; |
| } |
| } |
| |
| // Resume network start for headers_transaction. It will doom the existing |
| // entry and create a new entry due to validation returning a 200. |
| auto& c = context_list[2]; |
| c->trans->ResumeNetworkStart(); |
| |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_EQ(1, cache.GetCountWriterTransactions(kSimpleGET_Transaction.url)); |
| |
| // Complete the rest of the transactions. |
| for (int i = 0; i < kNumTransactions; i++) { |
| auto& context = context_list[i]; |
|