// Copyright 2012 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include #include #include #include "base/check_op.h" #include "base/functional/bind.h" #include "base/functional/callback.h" #include "base/functional/callback_helpers.h" #include "base/location.h" #include "base/memory/raw_ptr.h" #include "base/memory/ref_counted.h" #include "base/memory/weak_ptr.h" #include "base/notreached.h" #include "base/run_loop.h" #include "base/strings/string_number_conversions.h" #include "base/strings/stringprintf.h" #include "base/task/single_thread_task_runner.h" #include "base/test/scoped_feature_list.h" #include "base/threading/platform_thread.h" #include "base/time/time.h" #include "base/values.h" #include "net/base/features.h" #include "net/base/host_port_pair.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/network_anonymization_key.h" #include "net/base/privacy_mode.h" #include "net/base/proxy_server.h" #include "net/base/proxy_string_util.h" #include "net/base/request_priority.h" #include "net/base/schemeful_site.h" #include "net/base/test_completion_callback.h" #include "net/dns/public/resolve_error_info.h" #include "net/dns/public/secure_dns_policy.h" #include "net/http/http_response_headers.h" #include "net/http/http_response_info.h" #include "net/log/net_log.h" #include "net/log/net_log_event_type.h" #include "net/log/net_log_source.h" #include "net/log/net_log_source_type.h" #include "net/log/test_net_log.h" #include "net/log/test_net_log_util.h" #include "net/socket/client_socket_factory.h" #include "net/socket/client_socket_handle.h" #include "net/socket/connect_job_factory.h" #include "net/socket/datagram_client_socket.h" #include "net/socket/socket_performance_watcher.h" #include "net/socket/socket_tag.h" #include "net/socket/socket_test_util.h" #include "net/socket/ssl_client_socket.h" #include "net/socket/stream_socket.h" #include "net/socket/transport_client_socket_pool.h" #include "net/socket/transport_connect_job.h" #include "net/ssl/ssl_cert_request_info.h" #include "net/test/gtest_util.h" #include "net/test/test_with_task_environment.h" #include "net/traffic_annotation/network_traffic_annotation.h" #include "net/traffic_annotation/network_traffic_annotation_test_helper.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" #include "third_party/abseil-cpp/absl/types/optional.h" #include "url/scheme_host_port.h" #include "url/url_constants.h" using net::test::IsError; using net::test::IsOk; using ::testing::Invoke; using ::testing::Return; namespace net { namespace { const int kDefaultMaxSockets = 4; const int kDefaultMaxSocketsPerGroup = 2; constexpr base::TimeDelta kUnusedIdleSocketTimeout = base::Seconds(10); ClientSocketPool::GroupId TestGroupId( base::StringPiece host, int port = 80, base::StringPiece scheme = url::kHttpScheme, PrivacyMode privacy_mode = PrivacyMode::PRIVACY_MODE_DISABLED, NetworkAnonymizationKey network_anonymization_key = NetworkAnonymizationKey()) { return ClientSocketPool::GroupId(url::SchemeHostPort(scheme, host, port), privacy_mode, network_anonymization_key, SecureDnsPolicy::kAllow); } // Make sure |handle| sets load times correctly when it has been assigned a // reused socket. void TestLoadTimingInfoConnectedReused(const ClientSocketHandle& handle) { LoadTimingInfo load_timing_info; // Only pass true in as |is_reused|, as in general, HttpStream types should // have stricter concepts of reuse than socket pools. EXPECT_TRUE(handle.GetLoadTimingInfo(true, &load_timing_info)); EXPECT_EQ(true, load_timing_info.socket_reused); EXPECT_NE(NetLogSource::kInvalidId, load_timing_info.socket_log_id); ExpectConnectTimingHasNoTimes(load_timing_info.connect_timing); ExpectLoadTimingHasOnlyConnectionTimes(load_timing_info); } // Make sure |handle| sets load times correctly when it has been assigned a // fresh socket. Also runs TestLoadTimingInfoConnectedReused, since the owner // of a connection where |is_reused| is false may consider the connection // reused. void TestLoadTimingInfoConnectedNotReused(const ClientSocketHandle& handle) { EXPECT_FALSE(handle.is_reused()); LoadTimingInfo load_timing_info; EXPECT_TRUE(handle.GetLoadTimingInfo(false, &load_timing_info)); EXPECT_FALSE(load_timing_info.socket_reused); EXPECT_NE(NetLogSource::kInvalidId, load_timing_info.socket_log_id); ExpectConnectTimingHasTimes(load_timing_info.connect_timing, CONNECT_TIMING_HAS_CONNECT_TIMES_ONLY); ExpectLoadTimingHasOnlyConnectionTimes(load_timing_info); TestLoadTimingInfoConnectedReused(handle); } // Make sure |handle| sets load times correctly, in the case that it does not // currently have a socket. void TestLoadTimingInfoNotConnected(const ClientSocketHandle& handle) { // Should only be set to true once a socket is assigned, if at all. EXPECT_FALSE(handle.is_reused()); LoadTimingInfo load_timing_info; EXPECT_FALSE(handle.GetLoadTimingInfo(false, &load_timing_info)); EXPECT_FALSE(load_timing_info.socket_reused); EXPECT_EQ(NetLogSource::kInvalidId, load_timing_info.socket_log_id); ExpectConnectTimingHasNoTimes(load_timing_info.connect_timing); ExpectLoadTimingHasOnlyConnectionTimes(load_timing_info); } class MockClientSocket : public StreamSocket { public: explicit MockClientSocket(net::NetLog* net_log) : net_log_(NetLogWithSource::Make(net_log, NetLogSourceType::SOCKET)) {} MockClientSocket(const MockClientSocket&) = delete; MockClientSocket& operator=(const MockClientSocket&) = delete; // Sets whether the socket has unread data. If true, the next call to Read() // will return 1 byte and IsConnectedAndIdle() will return false. void set_has_unread_data(bool has_unread_data) { has_unread_data_ = has_unread_data; } // Socket implementation. int Read(IOBuffer* /* buf */, int len, CompletionOnceCallback /* callback */) override { if (has_unread_data_ && len > 0) { has_unread_data_ = false; was_used_to_convey_data_ = true; return 1; } return ERR_UNEXPECTED; } int Write( IOBuffer* /* buf */, int len, CompletionOnceCallback /* callback */, const NetworkTrafficAnnotationTag& /*traffic_annotation*/) override { was_used_to_convey_data_ = true; return len; } int SetReceiveBufferSize(int32_t size) override { return OK; } int SetSendBufferSize(int32_t size) override { return OK; } // StreamSocket implementation. int Connect(CompletionOnceCallback callback) override { connected_ = true; return OK; } void Disconnect() override { connected_ = false; } bool IsConnected() const override { return connected_; } bool IsConnectedAndIdle() const override { return connected_ && !has_unread_data_; } int GetPeerAddress(IPEndPoint* /* address */) const override { return ERR_UNEXPECTED; } int GetLocalAddress(IPEndPoint* /* address */) const override { return ERR_UNEXPECTED; } const NetLogWithSource& NetLog() const override { return net_log_; } bool WasEverUsed() const override { return was_used_to_convey_data_; } bool WasAlpnNegotiated() const override { return false; } NextProto GetNegotiatedProtocol() const override { return kProtoUnknown; } bool GetSSLInfo(SSLInfo* ssl_info) override { return false; } int64_t GetTotalReceivedBytes() const override { NOTIMPLEMENTED(); return 0; } void ApplySocketTag(const SocketTag& tag) override {} private: bool connected_ = false; bool has_unread_data_ = false; NetLogWithSource net_log_; bool was_used_to_convey_data_ = false; }; class TestConnectJob; class MockClientSocketFactory : public ClientSocketFactory { public: MockClientSocketFactory() = default; std::unique_ptr CreateDatagramClientSocket( DatagramSocket::BindType bind_type, NetLog* net_log, const NetLogSource& source) override { NOTREACHED(); return nullptr; } std::unique_ptr CreateTransportClientSocket( const AddressList& addresses, std::unique_ptr< SocketPerformanceWatcher> /* socket_performance_watcher */, NetworkQualityEstimator* /* network_quality_estimator */, NetLog* /* net_log */, const NetLogSource& /*source*/) override { allocation_count_++; return nullptr; } std::unique_ptr CreateSSLClientSocket( SSLClientContext* context, std::unique_ptr stream_socket, const HostPortPair& host_and_port, const SSLConfig& ssl_config) override { NOTIMPLEMENTED(); return nullptr; } void WaitForSignal(TestConnectJob* job) { waiting_jobs_.push_back(job); } void SignalJobs(); void SignalJob(size_t job); void SetJobLoadState(size_t job, LoadState load_state); // Sets the HasConnectionEstablished value of the specified job to true, // without invoking the callback. void SetJobHasEstablishedConnection(size_t job); int allocation_count() const { return allocation_count_; } private: int allocation_count_ = 0; std::vector waiting_jobs_; }; class TestConnectJob : public ConnectJob { public: enum JobType { kMockJob, kMockFailingJob, kMockPendingJob, kMockPendingFailingJob, kMockWaitingJob, // Certificate errors return a socket in addition to an error code. kMockCertErrorJob, kMockPendingCertErrorJob, kMockAdditionalErrorStateJob, kMockPendingAdditionalErrorStateJob, kMockUnreadDataJob, kMockAuthChallengeOnceJob, kMockAuthChallengeTwiceJob, kMockAuthChallengeOnceFailingJob, kMockAuthChallengeTwiceFailingJob, }; // The kMockPendingJob uses a slight delay before allowing the connect // to complete. static const int kPendingConnectDelay = 2; TestConnectJob(JobType job_type, RequestPriority request_priority, SocketTag socket_tag, base::TimeDelta timeout_duration, const CommonConnectJobParams* common_connect_job_params, ConnectJob::Delegate* delegate, MockClientSocketFactory* client_socket_factory) : ConnectJob(request_priority, socket_tag, timeout_duration, common_connect_job_params, delegate, nullptr /* net_log */, NetLogSourceType::TRANSPORT_CONNECT_JOB, NetLogEventType::TRANSPORT_CONNECT_JOB_CONNECT), job_type_(job_type), client_socket_factory_(client_socket_factory) {} TestConnectJob(const TestConnectJob&) = delete; TestConnectJob& operator=(const TestConnectJob&) = delete; void Signal() { DoConnect(waiting_success_, true /* async */, false /* recoverable */); } void set_load_state(LoadState load_state) { load_state_ = load_state; } void set_has_established_connection() { DCHECK(!has_established_connection_); has_established_connection_ = true; } // From ConnectJob: LoadState GetLoadState() const override { return load_state_; } bool HasEstablishedConnection() const override { return has_established_connection_; } ResolveErrorInfo GetResolveErrorInfo() const override { return ResolveErrorInfo(OK); } bool IsSSLError() const override { return store_additional_error_state_; } scoped_refptr GetCertRequestInfo() override { if (store_additional_error_state_) return base::MakeRefCounted(); return nullptr; } private: // From ConnectJob: int ConnectInternal() override { AddressList ignored; client_socket_factory_->CreateTransportClientSocket( ignored, nullptr, nullptr, nullptr, NetLogSource()); switch (job_type_) { case kMockJob: return DoConnect(true /* successful */, false /* sync */, false /* cert_error */); case kMockFailingJob: return DoConnect(false /* error */, false /* sync */, false /* cert_error */); case kMockPendingJob: set_load_state(LOAD_STATE_CONNECTING); // Depending on execution timings, posting a delayed task can result // in the task getting executed the at the earliest possible // opportunity or only after returning once from the message loop and // then a second call into the message loop. In order to make behavior // more deterministic, we change the default delay to 2ms. This should // always require us to wait for the second call into the message loop. // // N.B. The correct fix for this and similar timing problems is to // abstract time for the purpose of unittests. Unfortunately, we have // a lot of third-party components that directly call the various // time functions, so this change would be rather invasive. base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask( FROM_HERE, base::BindOnce(base::IgnoreResult(&TestConnectJob::DoConnect), weak_factory_.GetWeakPtr(), true /* successful */, true /* async */, false /* cert_error */), base::Milliseconds(kPendingConnectDelay)); return ERR_IO_PENDING; case kMockPendingFailingJob: set_load_state(LOAD_STATE_CONNECTING); base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask( FROM_HERE, base::BindOnce(base::IgnoreResult(&TestConnectJob::DoConnect), weak_factory_.GetWeakPtr(), false /* error */, true /* async */, false /* cert_error */), base::Milliseconds(2)); return ERR_IO_PENDING; case kMockWaitingJob: set_load_state(LOAD_STATE_CONNECTING); client_socket_factory_->WaitForSignal(this); waiting_success_ = true; return ERR_IO_PENDING; case kMockCertErrorJob: return DoConnect(false /* error */, false /* sync */, true /* cert_error */); case kMockPendingCertErrorJob: set_load_state(LOAD_STATE_CONNECTING); base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask( FROM_HERE, base::BindOnce(base::IgnoreResult(&TestConnectJob::DoConnect), weak_factory_.GetWeakPtr(), false /* error */, true /* async */, true /* cert_error */), base::Milliseconds(2)); return ERR_IO_PENDING; case kMockAdditionalErrorStateJob: store_additional_error_state_ = true; return DoConnect(false /* error */, false /* sync */, false /* cert_error */); case kMockPendingAdditionalErrorStateJob: set_load_state(LOAD_STATE_CONNECTING); store_additional_error_state_ = true; base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask( FROM_HERE, base::BindOnce(base::IgnoreResult(&TestConnectJob::DoConnect), weak_factory_.GetWeakPtr(), false /* error */, true /* async */, false /* cert_error */), base::Milliseconds(2)); return ERR_IO_PENDING; case kMockUnreadDataJob: { int ret = DoConnect(true /* successful */, false /* sync */, false /* cert_error */); static_cast(socket())->set_has_unread_data(true); return ret; } case kMockAuthChallengeOnceJob: set_load_state(LOAD_STATE_CONNECTING); DoAdvanceAuthChallenge(1, true /* succeed_after_last_challenge */); return ERR_IO_PENDING; case kMockAuthChallengeTwiceJob: set_load_state(LOAD_STATE_CONNECTING); DoAdvanceAuthChallenge(2, true /* succeed_after_last_challenge */); return ERR_IO_PENDING; case kMockAuthChallengeOnceFailingJob: set_load_state(LOAD_STATE_CONNECTING); DoAdvanceAuthChallenge(1, false /* succeed_after_last_challenge */); return ERR_IO_PENDING; case kMockAuthChallengeTwiceFailingJob: set_load_state(LOAD_STATE_CONNECTING); DoAdvanceAuthChallenge(2, false /* succeed_after_last_challenge */); return ERR_IO_PENDING; default: NOTREACHED(); SetSocket(std::unique_ptr(), absl::nullopt); return ERR_FAILED; } } void ChangePriorityInternal(RequestPriority priority) override {} int DoConnect(bool succeed, bool was_async, bool cert_error) { int result = OK; has_established_connection_ = true; if (succeed) { SetSocket(std::make_unique(net_log().net_log()), absl::nullopt); socket()->Connect(CompletionOnceCallback()); } else if (cert_error) { SetSocket(std::make_unique(net_log().net_log()), absl::nullopt); result = ERR_CERT_COMMON_NAME_INVALID; } else { result = ERR_CONNECTION_FAILED; SetSocket(std::unique_ptr(), absl::nullopt); } if (was_async) NotifyDelegateOfCompletion(result); return result; } void DoAdvanceAuthChallenge(int remaining_challenges, bool succeed_after_last_challenge) { base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask( FROM_HERE, base::BindOnce(&TestConnectJob::InvokeNextProxyAuthCallback, weak_factory_.GetWeakPtr(), remaining_challenges, succeed_after_last_challenge)); } void InvokeNextProxyAuthCallback(int remaining_challenges, bool succeed_after_last_challenge) { set_load_state(LOAD_STATE_ESTABLISHING_PROXY_TUNNEL); if (remaining_challenges == 0) { DoConnect(succeed_after_last_challenge, true /* was_async */, false /* cert_error */); return; } // Integration tests make sure HttpResponseInfo and HttpAuthController work. // The auth tests here are just focused on ConnectJob bookkeeping. HttpResponseInfo info; NotifyDelegateOfProxyAuth( info, nullptr /* http_auth_controller */, base::BindOnce(&TestConnectJob::DoAdvanceAuthChallenge, weak_factory_.GetWeakPtr(), remaining_challenges - 1, succeed_after_last_challenge)); } bool waiting_success_; const JobType job_type_; const raw_ptr client_socket_factory_; LoadState load_state_ = LOAD_STATE_IDLE; bool has_established_connection_ = false; bool store_additional_error_state_ = false; base::WeakPtrFactory weak_factory_{this}; }; class TestConnectJobFactory : public ConnectJobFactory { public: explicit TestConnectJobFactory(MockClientSocketFactory* client_socket_factory) : client_socket_factory_(client_socket_factory) {} TestConnectJobFactory(const TestConnectJobFactory&) = delete; TestConnectJobFactory& operator=(const TestConnectJobFactory&) = delete; ~TestConnectJobFactory() override = default; void set_job_type(TestConnectJob::JobType job_type) { job_type_ = job_type; } void set_job_types(std::list* job_types) { job_types_ = job_types; CHECK(!job_types_->empty()); } void set_timeout_duration(base::TimeDelta timeout_duration) { timeout_duration_ = timeout_duration; } // ConnectJobFactory implementation. std::unique_ptr CreateConnectJob( Endpoint endpoint, const ProxyServer& proxy_server, const absl::optional& proxy_annotation_tag, const SSLConfig* ssl_config_for_origin, const SSLConfig* ssl_config_for_proxy, bool force_tunnel, PrivacyMode privacy_mode, const OnHostResolutionCallback& resolution_callback, RequestPriority request_priority, SocketTag socket_tag, const NetworkAnonymizationKey& network_anonymization_key, SecureDnsPolicy secure_dns_policy, const CommonConnectJobParams* common_connect_job_params, ConnectJob::Delegate* delegate) const override { EXPECT_TRUE(!job_types_ || !job_types_->empty()); TestConnectJob::JobType job_type = job_type_; if (job_types_ && !job_types_->empty()) { job_type = job_types_->front(); job_types_->pop_front(); } return std::make_unique( job_type, request_priority, socket_tag, timeout_duration_, common_connect_job_params, delegate, client_socket_factory_); } private: TestConnectJob::JobType job_type_ = TestConnectJob::kMockJob; raw_ptr> job_types_ = nullptr; base::TimeDelta timeout_duration_; const raw_ptr client_socket_factory_; }; } // namespace namespace { void MockClientSocketFactory::SignalJobs() { for (auto* waiting_job : waiting_jobs_) { waiting_job->Signal(); } waiting_jobs_.clear(); } void MockClientSocketFactory::SignalJob(size_t job) { ASSERT_LT(job, waiting_jobs_.size()); waiting_jobs_[job]->Signal(); waiting_jobs_.erase(waiting_jobs_.begin() + job); } void MockClientSocketFactory::SetJobLoadState(size_t job, LoadState load_state) { ASSERT_LT(job, waiting_jobs_.size()); waiting_jobs_[job]->set_load_state(load_state); } void MockClientSocketFactory::SetJobHasEstablishedConnection(size_t job) { ASSERT_LT(job, waiting_jobs_.size()); waiting_jobs_[job]->set_has_established_connection(); } class ClientSocketPoolBaseTest : public TestWithTaskEnvironment { protected: ClientSocketPoolBaseTest() : TestWithTaskEnvironment( base::test::TaskEnvironment::TimeSource::MOCK_TIME), params_(ClientSocketPool::SocketParams::CreateForHttpForTesting()) { connect_backup_jobs_enabled_ = TransportClientSocketPool::connect_backup_jobs_enabled(); TransportClientSocketPool::set_connect_backup_jobs_enabled(true); } ~ClientSocketPoolBaseTest() override { TransportClientSocketPool::set_connect_backup_jobs_enabled( connect_backup_jobs_enabled_); } void CreatePool(int max_sockets, int max_sockets_per_group, bool enable_backup_connect_jobs = false) { CreatePoolWithIdleTimeouts(max_sockets, max_sockets_per_group, kUnusedIdleSocketTimeout, ClientSocketPool::used_idle_socket_timeout(), enable_backup_connect_jobs); } void CreatePoolWithIdleTimeouts( int max_sockets, int max_sockets_per_group, base::TimeDelta unused_idle_socket_timeout, base::TimeDelta used_idle_socket_timeout, bool enable_backup_connect_jobs = false, ProxyServer proxy_server = ProxyServer::Direct()) { DCHECK(!pool_.get()); std::unique_ptr connect_job_factory = std::make_unique(&client_socket_factory_); connect_job_factory_ = connect_job_factory.get(); pool_ = TransportClientSocketPool::CreateForTesting( max_sockets, max_sockets_per_group, unused_idle_socket_timeout, used_idle_socket_timeout, proxy_server, /*is_for_websockets=*/false, &common_connect_job_params_, std::move(connect_job_factory), nullptr /* ssl_config_service */, enable_backup_connect_jobs); } int StartRequestWithIgnoreLimits( const ClientSocketPool::GroupId& group_id, RequestPriority priority, ClientSocketPool::RespectLimits respect_limits) { return test_base_.StartRequestUsingPool(pool_.get(), group_id, priority, respect_limits, params_); } int StartRequest(const ClientSocketPool::GroupId& group_id, RequestPriority priority) { return StartRequestWithIgnoreLimits( group_id, priority, ClientSocketPool::RespectLimits::ENABLED); } int GetOrderOfRequest(size_t index) const { return test_base_.GetOrderOfRequest(index); } bool ReleaseOneConnection(ClientSocketPoolTest::KeepAlive keep_alive) { return test_base_.ReleaseOneConnection(keep_alive); } void ReleaseAllConnections(ClientSocketPoolTest::KeepAlive keep_alive) { test_base_.ReleaseAllConnections(keep_alive); } // Expects a single NetLogEventType::SOCKET_POOL_CLOSING_SOCKET in |net_log_|. // It should be logged for the provided source and have the indicated reason. void ExpectSocketClosedWithReason(NetLogSource expected_source, const char* expected_reason) { auto entries = net_log_observer_.GetEntriesForSourceWithType( expected_source, NetLogEventType::SOCKET_POOL_CLOSING_SOCKET, NetLogEventPhase::NONE); ASSERT_EQ(1u, entries.size()); ASSERT_TRUE(entries[0].HasParams()); const std::string* reason = entries[0].params.FindString("reason"); ASSERT_TRUE(reason); EXPECT_EQ(expected_reason, *reason); } TestSocketRequest* request(int i) { return test_base_.request(i); } size_t requests_size() const { return test_base_.requests_size(); } std::vector>* requests() { return test_base_.requests(); } // Only counts the requests that get sockets asynchronously; // synchronous completions are not registered by this count. size_t completion_count() const { return test_base_.completion_count(); } const CommonConnectJobParams common_connect_job_params_{ nullptr /* client_socket_factory */, nullptr /* host_resolver */, nullptr /* http_auth_cache */, nullptr /* http_auth_handler_factory */, nullptr /* spdy_session_pool */, nullptr /* quic_supported_versions */, nullptr /* quic_stream_factory */, nullptr /* proxy_delegate */, nullptr /* http_user_agent_settings */, nullptr /* ssl_client_context */, nullptr /* socket_performance_watcher_factory */, nullptr /* network_quality_estimator */, NetLog::Get(), nullptr /* websocket_endpoint_lock_manager */}; bool connect_backup_jobs_enabled_; MockClientSocketFactory client_socket_factory_; RecordingNetLogObserver net_log_observer_; // These parameters are never actually used to create a TransportConnectJob. scoped_refptr params_; // Must outlive `connect_job_factory_` std::unique_ptr pool_; raw_ptr connect_job_factory_; ClientSocketPoolTest test_base_; }; TEST_F(ClientSocketPoolBaseTest, BasicSynchronous) { CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); TestCompletionCallback callback; ClientSocketHandle handle; NetLogWithSource net_log_with_source = NetLogWithSource::Make(NetLogSourceType::NONE); TestLoadTimingInfoNotConnected(handle); EXPECT_EQ(OK, handle.Init( TestGroupId("a"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), net_log_with_source)); EXPECT_TRUE(handle.is_initialized()); EXPECT_TRUE(handle.socket()); TestLoadTimingInfoConnectedNotReused(handle); handle.Reset(); TestLoadTimingInfoNotConnected(handle); auto entries = net_log_observer_.GetEntriesForSource(net_log_with_source.source()); EXPECT_EQ(5u, entries.size()); EXPECT_TRUE(LogContainsEvent( entries, 0, NetLogEventType::TCP_CLIENT_SOCKET_POOL_REQUESTED_SOCKET, NetLogEventPhase::NONE)); EXPECT_TRUE(LogContainsBeginEvent(entries, 1, NetLogEventType::SOCKET_POOL)); EXPECT_TRUE(LogContainsEvent( entries, 2, NetLogEventType::SOCKET_POOL_BOUND_TO_CONNECT_JOB, NetLogEventPhase::NONE)); EXPECT_TRUE(LogContainsEvent(entries, 3, NetLogEventType::SOCKET_POOL_BOUND_TO_SOCKET, NetLogEventPhase::NONE)); EXPECT_TRUE(LogContainsEndEvent(entries, 4, NetLogEventType::SOCKET_POOL)); } TEST_F(ClientSocketPoolBaseTest, InitConnectionFailure) { CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); connect_job_factory_->set_job_type(TestConnectJob::kMockFailingJob); NetLogWithSource net_log_with_source = NetLogWithSource::Make(NetLogSourceType::NONE); ClientSocketHandle handle; TestCompletionCallback callback; // Set the additional error state members to ensure that they get cleared. handle.set_is_ssl_error(true); handle.set_ssl_cert_request_info(base::MakeRefCounted()); EXPECT_EQ( ERR_CONNECTION_FAILED, handle.Init(TestGroupId("a"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), net_log_with_source)); EXPECT_FALSE(handle.socket()); EXPECT_FALSE(handle.is_ssl_error()); EXPECT_FALSE(handle.ssl_cert_request_info()); TestLoadTimingInfoNotConnected(handle); auto entries = net_log_observer_.GetEntriesForSource(net_log_with_source.source()); EXPECT_EQ(4u, entries.size()); EXPECT_TRUE(LogContainsEvent( entries, 0, NetLogEventType::TCP_CLIENT_SOCKET_POOL_REQUESTED_SOCKET, NetLogEventPhase::NONE)); EXPECT_TRUE(LogContainsBeginEvent(entries, 1, NetLogEventType::SOCKET_POOL)); EXPECT_TRUE(LogContainsEvent( entries, 2, NetLogEventType::SOCKET_POOL_BOUND_TO_CONNECT_JOB, NetLogEventPhase::NONE)); EXPECT_TRUE(LogContainsEndEvent(entries, 3, NetLogEventType::SOCKET_POOL)); } // Test releasing an open socket into the socket pool, telling the socket pool // to close the socket. TEST_F(ClientSocketPoolBaseTest, ReleaseAndCloseConnection) { CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); EXPECT_THAT(StartRequest(TestGroupId("a"), LOWEST), IsError(OK)); ASSERT_TRUE(request(0)->handle()->socket()); net::NetLogSource source = request(0)->handle()->socket()->NetLog().source(); ReleaseOneConnection(ClientSocketPoolTest::NO_KEEP_ALIVE); EXPECT_EQ(0, pool_->IdleSocketCount()); EXPECT_FALSE(pool_->HasGroupForTesting(TestGroupId("a"))); ExpectSocketClosedWithReason( source, TransportClientSocketPool::kClosedConnectionReturnedToPool); } TEST_F(ClientSocketPoolBaseTest, SocketWithUnreadDataReturnedToPool) { CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); connect_job_factory_->set_job_type(TestConnectJob::kMockUnreadDataJob); EXPECT_THAT(StartRequest(TestGroupId("a"), LOWEST), IsError(OK)); ASSERT_TRUE(request(0)->handle()->socket()); net::NetLogSource source = request(0)->handle()->socket()->NetLog().source(); EXPECT_TRUE(request(0)->handle()->socket()->IsConnected()); EXPECT_FALSE(request(0)->handle()->socket()->IsConnectedAndIdle()); ReleaseOneConnection(ClientSocketPoolTest::KEEP_ALIVE); EXPECT_EQ(0, pool_->IdleSocketCount()); EXPECT_FALSE(pool_->HasGroupForTesting(TestGroupId("a"))); ExpectSocketClosedWithReason( source, TransportClientSocketPool::kDataReceivedUnexpectedly); } // Make sure different groups do not share sockets. TEST_F(ClientSocketPoolBaseTest, GroupSeparation) { base::test::ScopedFeatureList feature_list; feature_list.InitAndEnableFeature( features::kPartitionConnectionsByNetworkIsolationKey); CreatePool(1000 /* max_sockets */, 2 /* max_sockets_per_group */); const HostPortPair kHostPortPairs[] = { {"a", 80}, {"a", 443}, {"b", 80}, }; const char* const kSchemes[] = { url::kHttpScheme, url::kHttpsScheme, }; const PrivacyMode kPrivacyModes[] = {PrivacyMode::PRIVACY_MODE_DISABLED, PrivacyMode::PRIVACY_MODE_ENABLED}; const SchemefulSite kSiteA(GURL("http://a.test/")); const SchemefulSite kSiteB(GURL("http://b.test/")); const NetworkAnonymizationKey kNetworkAnonymizationKeys[] = { NetworkAnonymizationKey::CreateSameSite(kSiteA), NetworkAnonymizationKey::CreateSameSite(kSiteB), }; const SecureDnsPolicy kSecureDnsPolicys[] = {SecureDnsPolicy::kAllow, SecureDnsPolicy::kDisable}; int total_idle_sockets = 0; // Walk through each GroupId, making sure that requesting a socket for one // group does not return a previously connected socket for another group. for (const auto& host_port_pair : kHostPortPairs) { SCOPED_TRACE(host_port_pair.ToString()); for (const char* scheme : kSchemes) { SCOPED_TRACE(scheme); for (const auto& privacy_mode : kPrivacyModes) { SCOPED_TRACE(privacy_mode); for (const auto& network_anonymization_key : kNetworkAnonymizationKeys) { SCOPED_TRACE(network_anonymization_key.ToDebugString()); for (const auto& secure_dns_policy : kSecureDnsPolicys) { SCOPED_TRACE(static_cast(secure_dns_policy)); connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob); ClientSocketPool::GroupId group_id( url::SchemeHostPort(scheme, host_port_pair.host(), host_port_pair.port()), privacy_mode, network_anonymization_key, secure_dns_policy); EXPECT_FALSE(pool_->HasGroupForTesting(group_id)); TestCompletionCallback callback; ClientSocketHandle handle; // Since the group is empty, requesting a socket should not complete // synchronously. EXPECT_THAT(handle.Init(group_id, params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource()), IsError(ERR_IO_PENDING)); EXPECT_TRUE(pool_->HasGroupForTesting(group_id)); EXPECT_EQ(total_idle_sockets, pool_->IdleSocketCount()); EXPECT_THAT(callback.WaitForResult(), IsOk()); EXPECT_TRUE(handle.socket()); EXPECT_TRUE(pool_->HasGroupForTesting(group_id)); EXPECT_EQ(total_idle_sockets, pool_->IdleSocketCount()); // Return socket to pool. handle.Reset(); EXPECT_EQ(total_idle_sockets + 1, pool_->IdleSocketCount()); // Requesting a socket again should return the same socket as // before, so should complete synchronously. EXPECT_THAT(handle.Init(group_id, params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource()), IsOk()); EXPECT_TRUE(handle.socket()); EXPECT_EQ(total_idle_sockets, pool_->IdleSocketCount()); // Return socket to pool again. handle.Reset(); EXPECT_EQ(total_idle_sockets + 1, pool_->IdleSocketCount()); ++total_idle_sockets; } } } } } } TEST_F(ClientSocketPoolBaseTest, TotalLimit) { CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); // TODO(eroman): Check that the NetLog contains this event. EXPECT_THAT(StartRequest(TestGroupId("a"), DEFAULT_PRIORITY), IsOk()); EXPECT_THAT(StartRequest(TestGroupId("b"), DEFAULT_PRIORITY), IsOk()); EXPECT_THAT(StartRequest(TestGroupId("c"), DEFAULT_PRIORITY), IsOk()); EXPECT_THAT(StartRequest(TestGroupId("d"), DEFAULT_PRIORITY), IsOk()); EXPECT_EQ(static_cast(requests_size()), client_socket_factory_.allocation_count()); EXPECT_EQ(requests_size() - kDefaultMaxSockets, completion_count()); EXPECT_THAT(StartRequest(TestGroupId("e"), DEFAULT_PRIORITY), IsError(ERR_IO_PENDING)); EXPECT_THAT(StartRequest(TestGroupId("f"), DEFAULT_PRIORITY), IsError(ERR_IO_PENDING)); EXPECT_THAT(StartRequest(TestGroupId("g"), DEFAULT_PRIORITY), IsError(ERR_IO_PENDING)); ReleaseAllConnections(ClientSocketPoolTest::NO_KEEP_ALIVE); EXPECT_EQ(static_cast(requests_size()), client_socket_factory_.allocation_count()); EXPECT_EQ(requests_size() - kDefaultMaxSockets, completion_count()); EXPECT_EQ(1, GetOrderOfRequest(1)); EXPECT_EQ(2, GetOrderOfRequest(2)); EXPECT_EQ(3, GetOrderOfRequest(3)); EXPECT_EQ(4, GetOrderOfRequest(4)); EXPECT_EQ(5, GetOrderOfRequest(5)); EXPECT_EQ(6, GetOrderOfRequest(6)); EXPECT_EQ(7, GetOrderOfRequest(7)); // Make sure we test order of all requests made. EXPECT_EQ(ClientSocketPoolTest::kIndexOutOfBounds, GetOrderOfRequest(8)); } TEST_F(ClientSocketPoolBaseTest, TotalLimitReachedNewGroup) { CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); // TODO(eroman): Check that the NetLog contains this event. // Reach all limits: max total sockets, and max sockets per group. EXPECT_THAT(StartRequest(TestGroupId("a"), DEFAULT_PRIORITY), IsOk()); EXPECT_THAT(StartRequest(TestGroupId("a"), DEFAULT_PRIORITY), IsOk()); EXPECT_THAT(StartRequest(TestGroupId("b"), DEFAULT_PRIORITY), IsOk()); EXPECT_THAT(StartRequest(TestGroupId("b"), DEFAULT_PRIORITY), IsOk()); EXPECT_EQ(static_cast(requests_size()), client_socket_factory_.allocation_count()); EXPECT_EQ(requests_size() - kDefaultMaxSockets, completion_count()); // Now create a new group and verify that we don't starve it. EXPECT_THAT(StartRequest(TestGroupId("c"), DEFAULT_PRIORITY), IsError(ERR_IO_PENDING)); ReleaseAllConnections(ClientSocketPoolTest::NO_KEEP_ALIVE); EXPECT_EQ(static_cast(requests_size()), client_socket_factory_.allocation_count()); EXPECT_EQ(requests_size() - kDefaultMaxSockets, completion_count()); EXPECT_EQ(1, GetOrderOfRequest(1)); EXPECT_EQ(2, GetOrderOfRequest(2)); EXPECT_EQ(3, GetOrderOfRequest(3)); EXPECT_EQ(4, GetOrderOfRequest(4)); EXPECT_EQ(5, GetOrderOfRequest(5)); // Make sure we test order of all requests made. EXPECT_EQ(ClientSocketPoolTest::kIndexOutOfBounds, GetOrderOfRequest(6)); } TEST_F(ClientSocketPoolBaseTest, TotalLimitRespectsPriority) { CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); EXPECT_THAT(StartRequest(TestGroupId("b"), LOWEST), IsOk()); EXPECT_THAT(StartRequest(TestGroupId("a"), MEDIUM), IsOk()); EXPECT_THAT(StartRequest(TestGroupId("b"), HIGHEST), IsOk()); EXPECT_THAT(StartRequest(TestGroupId("a"), LOWEST), IsOk()); EXPECT_EQ(static_cast(requests_size()), client_socket_factory_.allocation_count()); EXPECT_THAT(StartRequest(TestGroupId("c"), LOWEST), IsError(ERR_IO_PENDING)); EXPECT_THAT(StartRequest(TestGroupId("a"), MEDIUM), IsError(ERR_IO_PENDING)); EXPECT_THAT(StartRequest(TestGroupId("b"), HIGHEST), IsError(ERR_IO_PENDING)); ReleaseAllConnections(ClientSocketPoolTest::NO_KEEP_ALIVE); EXPECT_EQ(requests_size() - kDefaultMaxSockets, completion_count()); // First 4 requests don't have to wait, and finish in order. EXPECT_EQ(1, GetOrderOfRequest(1)); EXPECT_EQ(2, GetOrderOfRequest(2)); EXPECT_EQ(3, GetOrderOfRequest(3)); EXPECT_EQ(4, GetOrderOfRequest(4)); // Request ("b", HIGHEST) has the highest priority, then (TestGroupId("a"), // MEDIUM), and then ("c", LOWEST). EXPECT_EQ(7, GetOrderOfRequest(5)); EXPECT_EQ(6, GetOrderOfRequest(6)); EXPECT_EQ(5, GetOrderOfRequest(7)); // Make sure we test order of all requests made. EXPECT_EQ(ClientSocketPoolTest::kIndexOutOfBounds, GetOrderOfRequest(9)); } // Test reprioritizing a request before completion doesn't interfere with // its completion. TEST_F(ClientSocketPoolBaseTest, ReprioritizeOne) { CreatePool(kDefaultMaxSockets, 1); EXPECT_THAT(StartRequest(TestGroupId("a"), LOWEST), IsError(OK)); EXPECT_THAT(StartRequest(TestGroupId("a"), MEDIUM), IsError(ERR_IO_PENDING)); EXPECT_TRUE(request(0)->handle()->socket()); EXPECT_FALSE(request(1)->handle()->socket()); request(1)->handle()->SetPriority(HIGHEST); ReleaseOneConnection(ClientSocketPoolTest::NO_KEEP_ALIVE); EXPECT_TRUE(request(1)->handle()->socket()); } // Reprioritize a request up past another one and make sure that changes the // completion order. TEST_F(ClientSocketPoolBaseTest, ReprioritizeUpReorder) { CreatePool(kDefaultMaxSockets, 1); EXPECT_THAT(StartRequest(TestGroupId("a"), LOWEST), IsError(OK)); EXPECT_THAT(StartRequest(TestGroupId("a"), MEDIUM), IsError(ERR_IO_PENDING)); EXPECT_THAT(StartRequest(TestGroupId("a"), LOWEST), IsError(ERR_IO_PENDING)); EXPECT_TRUE(request(0)->handle()->socket()); EXPECT_FALSE(request(1)->handle()->socket()); EXPECT_FALSE(request(2)->handle()->socket()); request(2)->handle()->SetPriority(HIGHEST); ReleaseAllConnections(ClientSocketPoolTest::NO_KEEP_ALIVE); EXPECT_EQ(1, GetOrderOfRequest(1)); EXPECT_EQ(3, GetOrderOfRequest(2)); EXPECT_EQ(2, GetOrderOfRequest(3)); } // Reprioritize a request without changing relative priorities and check // that the order doesn't change. TEST_F(ClientSocketPoolBaseTest, ReprioritizeUpNoReorder) { CreatePool(kDefaultMaxSockets, 1); EXPECT_THAT(StartRequest(TestGroupId("a"), LOWEST), IsError(OK)); EXPECT_THAT(StartRequest(TestGroupId("a"), MEDIUM), IsError(ERR_IO_PENDING)); EXPECT_THAT(StartRequest(TestGroupId("a"), LOW), IsError(ERR_IO_PENDING)); EXPECT_TRUE(request(0)->handle()->socket()); EXPECT_FALSE(request(1)->handle()->socket()); EXPECT_FALSE(request(2)->handle()->socket()); request(2)->handle()->SetPriority(MEDIUM); ReleaseAllConnections(ClientSocketPoolTest::NO_KEEP_ALIVE); EXPECT_EQ(1, GetOrderOfRequest(1)); EXPECT_EQ(2, GetOrderOfRequest(2)); EXPECT_EQ(3, GetOrderOfRequest(3)); } // Reprioritize a request past down another one and make sure that changes the // completion order. TEST_F(ClientSocketPoolBaseTest, ReprioritizeDownReorder) { CreatePool(kDefaultMaxSockets, 1); EXPECT_THAT(StartRequest(TestGroupId("a"), LOWEST), IsError(OK)); EXPECT_THAT(StartRequest(TestGroupId("a"), HIGHEST), IsError(ERR_IO_PENDING)); EXPECT_THAT(StartRequest(TestGroupId("a"), MEDIUM), IsError(ERR_IO_PENDING)); EXPECT_TRUE(request(0)->handle()->socket()); EXPECT_FALSE(request(1)->handle()->socket()); EXPECT_FALSE(request(2)->handle()->socket()); request(1)->handle()->SetPriority(LOW); ReleaseAllConnections(ClientSocketPoolTest::NO_KEEP_ALIVE); EXPECT_EQ(1, GetOrderOfRequest(1)); EXPECT_EQ(3, GetOrderOfRequest(2)); EXPECT_EQ(2, GetOrderOfRequest(3)); } // Reprioritize a request to the same level as another and confirm it is // put after the old request. TEST_F(ClientSocketPoolBaseTest, ReprioritizeResetFIFO) { CreatePool(kDefaultMaxSockets, 1); EXPECT_THAT(StartRequest(TestGroupId("a"), LOWEST), IsError(OK)); EXPECT_THAT(StartRequest(TestGroupId("a"), HIGHEST), IsError(ERR_IO_PENDING)); EXPECT_THAT(StartRequest(TestGroupId("a"), MEDIUM), IsError(ERR_IO_PENDING)); EXPECT_TRUE(request(0)->handle()->socket()); EXPECT_FALSE(request(1)->handle()->socket()); EXPECT_FALSE(request(2)->handle()->socket()); request(1)->handle()->SetPriority(MEDIUM); ReleaseAllConnections(ClientSocketPoolTest::NO_KEEP_ALIVE); EXPECT_EQ(1, GetOrderOfRequest(1)); EXPECT_EQ(3, GetOrderOfRequest(2)); EXPECT_EQ(2, GetOrderOfRequest(3)); } TEST_F(ClientSocketPoolBaseTest, TotalLimitRespectsGroupLimit) { CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); EXPECT_THAT(StartRequest(TestGroupId("a"), LOWEST), IsOk()); EXPECT_THAT(StartRequest(TestGroupId("a"), LOW), IsOk()); EXPECT_THAT(StartRequest(TestGroupId("b"), HIGHEST), IsOk()); EXPECT_THAT(StartRequest(TestGroupId("b"), MEDIUM), IsOk()); EXPECT_EQ(static_cast(requests_size()), client_socket_factory_.allocation_count()); EXPECT_THAT(StartRequest(TestGroupId("c"), MEDIUM), IsError(ERR_IO_PENDING)); EXPECT_THAT(StartRequest(TestGroupId("a"), LOW), IsError(ERR_IO_PENDING)); EXPECT_THAT(StartRequest(TestGroupId("b"), HIGHEST), IsError(ERR_IO_PENDING)); ReleaseAllConnections(ClientSocketPoolTest::NO_KEEP_ALIVE); EXPECT_EQ(static_cast(requests_size()), client_socket_factory_.allocation_count()); EXPECT_EQ(requests_size() - kDefaultMaxSockets, completion_count()); // First 4 requests don't have to wait, and finish in order. EXPECT_EQ(1, GetOrderOfRequest(1)); EXPECT_EQ(2, GetOrderOfRequest(2)); EXPECT_EQ(3, GetOrderOfRequest(3)); EXPECT_EQ(4, GetOrderOfRequest(4)); // Request ("b", 7) has the highest priority, but we can't make new socket for // group "b", because it has reached the per-group limit. Then we make // socket for ("c", 6), because it has higher priority than ("a", 4), // and we still can't make a socket for group "b". EXPECT_EQ(5, GetOrderOfRequest(5)); EXPECT_EQ(6, GetOrderOfRequest(6)); EXPECT_EQ(7, GetOrderOfRequest(7)); // Make sure we test order of all requests made. EXPECT_EQ(ClientSocketPoolTest::kIndexOutOfBounds, GetOrderOfRequest(8)); } // Make sure that we count connecting sockets against the total limit. TEST_F(ClientSocketPoolBaseTest, TotalLimitCountsConnectingSockets) { CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); EXPECT_THAT(StartRequest(TestGroupId("a"), DEFAULT_PRIORITY), IsOk()); EXPECT_THAT(StartRequest(TestGroupId("b"), DEFAULT_PRIORITY), IsOk()); EXPECT_THAT(StartRequest(TestGroupId("c"), DEFAULT_PRIORITY), IsOk()); // Create one asynchronous request. connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob); EXPECT_THAT(StartRequest(TestGroupId("d"), DEFAULT_PRIORITY), IsError(ERR_IO_PENDING)); // We post all of our delayed tasks with a 2ms delay. I.e. they don't // actually become pending until 2ms after they have been created. In order // to flush all tasks, we need to wait so that we know there are no // soon-to-be-pending tasks waiting. FastForwardBy(base::Milliseconds(10)); // The next synchronous request should wait for its turn. connect_job_factory_->set_job_type(TestConnectJob::kMockJob); EXPECT_THAT(StartRequest(TestGroupId("e"), DEFAULT_PRIORITY), IsError(ERR_IO_PENDING)); ReleaseAllConnections(ClientSocketPoolTest::NO_KEEP_ALIVE); EXPECT_EQ(static_cast(requests_size()), client_socket_factory_.allocation_count()); EXPECT_EQ(1, GetOrderOfRequest(1)); EXPECT_EQ(2, GetOrderOfRequest(2)); EXPECT_EQ(3, GetOrderOfRequest(3)); EXPECT_EQ(4, GetOrderOfRequest(4)); EXPECT_EQ(5, GetOrderOfRequest(5)); // Make sure we test order of all requests made. EXPECT_EQ(ClientSocketPoolTest::kIndexOutOfBounds, GetOrderOfRequest(6)); } TEST_F(ClientSocketPoolBaseTest, CorrectlyCountStalledGroups) { CreatePool(kDefaultMaxSockets, kDefaultMaxSockets); connect_job_factory_->set_job_type(TestConnectJob::kMockJob); EXPECT_THAT(StartRequest(TestGroupId("a"), DEFAULT_PRIORITY), IsOk()); EXPECT_THAT(StartRequest(TestGroupId("a"), DEFAULT_PRIORITY), IsOk()); EXPECT_THAT(StartRequest(TestGroupId("a"), DEFAULT_PRIORITY), IsOk()); EXPECT_THAT(StartRequest(TestGroupId("a"), DEFAULT_PRIORITY), IsOk()); connect_job_factory_->set_job_type(TestConnectJob::kMockWaitingJob); EXPECT_EQ(kDefaultMaxSockets, client_socket_factory_.allocation_count()); EXPECT_THAT(StartRequest(TestGroupId("b"), DEFAULT_PRIORITY), IsError(ERR_IO_PENDING)); EXPECT_THAT(StartRequest(TestGroupId("c"), DEFAULT_PRIORITY), IsError(ERR_IO_PENDING)); EXPECT_EQ(kDefaultMaxSockets, client_socket_factory_.allocation_count()); EXPECT_TRUE(ReleaseOneConnection(ClientSocketPoolTest::KEEP_ALIVE)); EXPECT_EQ(kDefaultMaxSockets + 1, client_socket_factory_.allocation_count()); EXPECT_TRUE(ReleaseOneConnection(ClientSocketPoolTest::KEEP_ALIVE)); EXPECT_EQ(kDefaultMaxSockets + 2, client_socket_factory_.allocation_count()); EXPECT_TRUE(ReleaseOneConnection(ClientSocketPoolTest::KEEP_ALIVE)); EXPECT_TRUE(ReleaseOneConnection(ClientSocketPoolTest::KEEP_ALIVE)); EXPECT_EQ(kDefaultMaxSockets + 2, client_socket_factory_.allocation_count()); } TEST_F(ClientSocketPoolBaseTest, StallAndThenCancelAndTriggerAvailableSocket) { CreatePool(kDefaultMaxSockets, kDefaultMaxSockets); connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob); TestCompletionCallback callback; ClientSocketHandle stalled_handle; EXPECT_EQ(ERR_IO_PENDING, stalled_handle.Init( TestGroupId("a"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); ClientSocketHandle handles[4]; for (auto& handle : handles) { EXPECT_EQ( ERR_IO_PENDING, handle.Init(TestGroupId("b"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); } // One will be stalled, cancel all the handles now. // This should hit the OnAvailableSocketSlot() code where we previously had // stalled groups, but no longer have any. for (auto& handle : handles) handle.Reset(); } TEST_F(ClientSocketPoolBaseTest, CancelStalledSocketAtSocketLimit) { CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); connect_job_factory_->set_job_type(TestConnectJob::kMockJob); { ClientSocketHandle handles[kDefaultMaxSockets]; TestCompletionCallback callbacks[kDefaultMaxSockets]; for (int i = 0; i < kDefaultMaxSockets; ++i) { EXPECT_EQ(OK, handles[i].Init(TestGroupId("a" + base::NumberToString(i)), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callbacks[i].callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); } // Force a stalled group. ClientSocketHandle stalled_handle; TestCompletionCallback callback; EXPECT_EQ(ERR_IO_PENDING, stalled_handle.Init( TestGroupId("foo"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); // Cancel the stalled request. stalled_handle.Reset(); EXPECT_EQ(kDefaultMaxSockets, client_socket_factory_.allocation_count()); EXPECT_EQ(0, pool_->IdleSocketCount()); // Dropping out of scope will close all handles and return them to idle. } EXPECT_EQ(kDefaultMaxSockets, client_socket_factory_.allocation_count()); EXPECT_EQ(kDefaultMaxSockets, pool_->IdleSocketCount()); } TEST_F(ClientSocketPoolBaseTest, CancelPendingSocketAtSocketLimit) { CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); connect_job_factory_->set_job_type(TestConnectJob::kMockWaitingJob); { ClientSocketHandle handles[kDefaultMaxSockets]; for (int i = 0; i < kDefaultMaxSockets; ++i) { TestCompletionCallback callback; EXPECT_EQ(ERR_IO_PENDING, handles[i].Init( TestGroupId("a" + base::NumberToString(i)), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); } // Force a stalled group. connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob); ClientSocketHandle stalled_handle; TestCompletionCallback callback; EXPECT_EQ(ERR_IO_PENDING, stalled_handle.Init( TestGroupId("foo"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); // Since it is stalled, it should have no connect jobs. EXPECT_EQ(0u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("foo"))); EXPECT_EQ(0u, pool_->NumNeverAssignedConnectJobsInGroupForTesting( TestGroupId("foo"))); EXPECT_EQ(0u, pool_->NumUnassignedConnectJobsInGroupForTesting( TestGroupId("foo"))); // Cancel the stalled request. handles[0].Reset(); // Now we should have a connect job. EXPECT_EQ(1u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("foo"))); EXPECT_EQ(0u, pool_->NumNeverAssignedConnectJobsInGroupForTesting( TestGroupId("foo"))); EXPECT_EQ(0u, pool_->NumUnassignedConnectJobsInGroupForTesting( TestGroupId("foo"))); // The stalled socket should connect. EXPECT_THAT(callback.WaitForResult(), IsOk()); EXPECT_EQ(kDefaultMaxSockets + 1, client_socket_factory_.allocation_count()); EXPECT_EQ(0, pool_->IdleSocketCount()); EXPECT_EQ(0u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("foo"))); EXPECT_EQ(0u, pool_->NumNeverAssignedConnectJobsInGroupForTesting( TestGroupId("foo"))); EXPECT_EQ(0u, pool_->NumUnassignedConnectJobsInGroupForTesting( TestGroupId("foo"))); // Dropping out of scope will close all handles and return them to idle. } EXPECT_EQ(1, pool_->IdleSocketCount()); } TEST_F(ClientSocketPoolBaseTest, WaitForStalledSocketAtSocketLimit) { CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); connect_job_factory_->set_job_type(TestConnectJob::kMockJob); ClientSocketHandle stalled_handle; TestCompletionCallback callback; { EXPECT_FALSE(pool_->IsStalled()); ClientSocketHandle handles[kDefaultMaxSockets]; for (int i = 0; i < kDefaultMaxSockets; ++i) { EXPECT_EQ( OK, handles[i].Init( TestGroupId(base::StringPrintf("take-2-%d", i)), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); } EXPECT_EQ(kDefaultMaxSockets, client_socket_factory_.allocation_count()); EXPECT_EQ(0, pool_->IdleSocketCount()); EXPECT_FALSE(pool_->IsStalled()); // Now we will hit the socket limit. EXPECT_EQ(ERR_IO_PENDING, stalled_handle.Init( TestGroupId("foo"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); EXPECT_TRUE(pool_->IsStalled()); // Dropping out of scope will close all handles and return them to idle. } // But if we wait for it, the released idle sockets will be closed in // preference of the waiting request. EXPECT_THAT(callback.WaitForResult(), IsOk()); EXPECT_EQ(kDefaultMaxSockets + 1, client_socket_factory_.allocation_count()); EXPECT_EQ(3, pool_->IdleSocketCount()); } // Regression test for http://crbug.com/40952. TEST_F(ClientSocketPoolBaseTest, CloseIdleSocketAtSocketLimitDeleteGroup) { CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup, true /* enable_backup_connect_jobs */); connect_job_factory_->set_job_type(TestConnectJob::kMockJob); for (int i = 0; i < kDefaultMaxSockets; ++i) { ClientSocketHandle handle; TestCompletionCallback callback; EXPECT_EQ( OK, handle.Init(TestGroupId("a" + base::NumberToString(i)), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); } // Flush all the DoReleaseSocket tasks. base::RunLoop().RunUntilIdle(); // Stall a group. Set a pending job so it'll trigger a backup job if we don't // reuse a socket. connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob); ClientSocketHandle handle; TestCompletionCallback callback; // "a0" is special here, since it should be the first entry in the sorted map, // which is the one which we would close an idle socket for. We shouldn't // close an idle socket though, since we should reuse the idle socket. EXPECT_EQ(OK, handle.Init( TestGroupId("a0"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); EXPECT_EQ(kDefaultMaxSockets, client_socket_factory_.allocation_count()); EXPECT_EQ(kDefaultMaxSockets - 1, pool_->IdleSocketCount()); } TEST_F(ClientSocketPoolBaseTest, PendingRequests) { CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); EXPECT_THAT(StartRequest(TestGroupId("a"), DEFAULT_PRIORITY), IsOk()); EXPECT_THAT(StartRequest(TestGroupId("a"), DEFAULT_PRIORITY), IsOk()); EXPECT_THAT(StartRequest(TestGroupId("a"), IDLE), IsError(ERR_IO_PENDING)); EXPECT_THAT(StartRequest(TestGroupId("a"), LOWEST), IsError(ERR_IO_PENDING)); EXPECT_THAT(StartRequest(TestGroupId("a"), MEDIUM), IsError(ERR_IO_PENDING)); EXPECT_THAT(StartRequest(TestGroupId("a"), HIGHEST), IsError(ERR_IO_PENDING)); EXPECT_THAT(StartRequest(TestGroupId("a"), LOW), IsError(ERR_IO_PENDING)); EXPECT_THAT(StartRequest(TestGroupId("a"), LOWEST), IsError(ERR_IO_PENDING)); ReleaseAllConnections(ClientSocketPoolTest::KEEP_ALIVE); EXPECT_EQ(kDefaultMaxSocketsPerGroup, client_socket_factory_.allocation_count()); EXPECT_EQ(requests_size() - kDefaultMaxSocketsPerGroup, completion_count()); EXPECT_EQ(1, GetOrderOfRequest(1)); EXPECT_EQ(2, GetOrderOfRequest(2)); EXPECT_EQ(8, GetOrderOfRequest(3)); EXPECT_EQ(6, GetOrderOfRequest(4)); EXPECT_EQ(4, GetOrderOfRequest(5)); EXPECT_EQ(3, GetOrderOfRequest(6)); EXPECT_EQ(5, GetOrderOfRequest(7)); EXPECT_EQ(7, GetOrderOfRequest(8)); // Make sure we test order of all requests made. EXPECT_EQ(ClientSocketPoolTest::kIndexOutOfBounds, GetOrderOfRequest(9)); } TEST_F(ClientSocketPoolBaseTest, PendingRequests_NoKeepAlive) { CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); EXPECT_THAT(StartRequest(TestGroupId("a"), DEFAULT_PRIORITY), IsOk()); EXPECT_THAT(StartRequest(TestGroupId("a"), DEFAULT_PRIORITY), IsOk()); EXPECT_THAT(StartRequest(TestGroupId("a"), LOWEST), IsError(ERR_IO_PENDING)); EXPECT_THAT(StartRequest(TestGroupId("a"), MEDIUM), IsError(ERR_IO_PENDING)); EXPECT_THAT(StartRequest(TestGroupId("a"), HIGHEST), IsError(ERR_IO_PENDING)); EXPECT_THAT(StartRequest(TestGroupId("a"), LOW), IsError(ERR_IO_PENDING)); EXPECT_THAT(StartRequest(TestGroupId("a"), LOWEST), IsError(ERR_IO_PENDING)); ReleaseAllConnections(ClientSocketPoolTest::NO_KEEP_ALIVE); for (size_t i = kDefaultMaxSocketsPerGroup; i < requests_size(); ++i) EXPECT_THAT(request(i)->WaitForResult(), IsOk()); EXPECT_EQ(static_cast(requests_size()), client_socket_factory_.allocation_count()); EXPECT_EQ(requests_size() - kDefaultMaxSocketsPerGroup, completion_count()); } TEST_F(ClientSocketPoolBaseTest, ResetAndCloseSocket) { CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob); ClientSocketHandle handle; TestCompletionCallback callback; EXPECT_EQ( ERR_IO_PENDING, handle.Init(TestGroupId("a"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); EXPECT_THAT(callback.WaitForResult(), IsOk()); ASSERT_TRUE(pool_->HasGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->IdleSocketCountInGroup(TestGroupId("a"))); EXPECT_EQ(1, pool_->NumActiveSocketsInGroupForTesting(TestGroupId("a"))); handle.ResetAndCloseSocket(); EXPECT_FALSE(pool_->HasGroupForTesting(TestGroupId("a"))); } // This test will start up a socket request and then call Reset() on the handle. // The pending ConnectJob should not be destroyed. TEST_F(ClientSocketPoolBaseTest, CancelRequestKeepsConnectJob) { CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob); ClientSocketHandle handle; TestCompletionCallback callback; EXPECT_EQ( ERR_IO_PENDING, handle.Init(TestGroupId("a"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); handle.Reset(); ASSERT_TRUE(pool_->HasGroupForTesting(TestGroupId("a"))); EXPECT_EQ(1u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); } // This test will start up a socket request and then call ResetAndCloseSocket() // on the handle. The pending ConnectJob or connected socket should be // destroyed. TEST_F(ClientSocketPoolBaseTest, CancelRequestAndCloseSocket) { CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); // When true, the socket connects before it's canceled. for (bool cancel_when_callback_pending : {false, true}) { if (cancel_when_callback_pending) { connect_job_factory_->set_job_type(TestConnectJob::kMockWaitingJob); } else { connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob); } ClientSocketHandle handle; TestCompletionCallback callback; EXPECT_EQ( ERR_IO_PENDING, handle.Init(TestGroupId("a"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); ASSERT_TRUE(pool_->HasGroupForTesting(TestGroupId("a"))); EXPECT_EQ(1u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); if (cancel_when_callback_pending) { client_socket_factory_.SignalJobs(); ASSERT_TRUE(pool_->HasGroupForTesting(TestGroupId("a"))); EXPECT_EQ(1, pool_->NumActiveSocketsInGroupForTesting(TestGroupId("a"))); } handle.ResetAndCloseSocket(); ASSERT_FALSE(pool_->HasGroupForTesting(TestGroupId("a"))); } } TEST_F(ClientSocketPoolBaseTest, CancelRequestAndCloseSocketWhenMoreRequestsThanConnectJobs) { CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); // When true, the sockets connect before they're canceled. for (bool cancel_when_callback_pending : {false, true}) { if (cancel_when_callback_pending) { connect_job_factory_->set_job_type(TestConnectJob::kMockWaitingJob); } else { connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob); } std::vector> handles; TestCompletionCallback callback; // Make |kDefaultMaxSockets + 1| socket requests. for (int i = 0; i < kDefaultMaxSocketsPerGroup + 1; ++i) { std::unique_ptr handle = std::make_unique(); EXPECT_EQ(ERR_IO_PENDING, handle->Init( TestGroupId("a"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); handles.push_back(std::move(handle)); ASSERT_TRUE(pool_->HasGroupForTesting(TestGroupId("a"))); EXPECT_EQ( static_cast(std::min(i + 1, kDefaultMaxSocketsPerGroup)), pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); } if (cancel_when_callback_pending) { client_socket_factory_.SignalJobs(); ASSERT_TRUE(pool_->HasGroupForTesting(TestGroupId("a"))); EXPECT_EQ(kDefaultMaxSocketsPerGroup, pool_->NumActiveSocketsInGroupForTesting(TestGroupId("a"))); } // Calling ResetAndCloseSocket() on a handle should not cancel a ConnectJob // or close a socket, since there are more requests than ConnectJobs or // sockets. handles[kDefaultMaxSocketsPerGroup]->ResetAndCloseSocket(); ASSERT_TRUE(pool_->HasGroupForTesting(TestGroupId("a"))); if (cancel_when_callback_pending) { EXPECT_EQ(kDefaultMaxSocketsPerGroup, pool_->NumActiveSocketsInGroupForTesting(TestGroupId("a"))); } else { EXPECT_EQ(static_cast(kDefaultMaxSocketsPerGroup), pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); } // Calling ResetAndCloseSocket() on other handles should cancel a ConnectJob // or close a socket. for (int i = kDefaultMaxSocketsPerGroup - 1; i >= 0; --i) { handles[i]->ResetAndCloseSocket(); if (i > 0) { ASSERT_TRUE(pool_->HasGroupForTesting(TestGroupId("a"))); if (cancel_when_callback_pending) { EXPECT_EQ(i, pool_->NumActiveSocketsInGroupForTesting(TestGroupId("a"))); } else { EXPECT_EQ(static_cast(i), pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); } } else { EXPECT_FALSE(pool_->HasGroupForTesting(TestGroupId("a"))); } } } } TEST_F(ClientSocketPoolBaseTest, ConnectCancelConnect) { CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob); ClientSocketHandle handle; TestCompletionCallback callback; EXPECT_EQ( ERR_IO_PENDING, handle.Init(TestGroupId("a"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); handle.Reset(); ASSERT_TRUE(pool_->HasGroupForTesting(TestGroupId("a"))); EXPECT_EQ(1u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); // This will create a second ConnectJob, since the other ConnectJob was // previously assigned to a request. TestCompletionCallback callback2; EXPECT_EQ( ERR_IO_PENDING, handle.Init(TestGroupId("a"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback2.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); ASSERT_TRUE(pool_->HasGroupForTesting(TestGroupId("a"))); EXPECT_EQ(2u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_THAT(callback2.WaitForResult(), IsOk()); EXPECT_FALSE(callback.have_result()); ASSERT_TRUE(pool_->HasGroupForTesting(TestGroupId("a"))); // One ConnectJob completed, and its socket is now assigned to |handle|. EXPECT_EQ(1, pool_->NumActiveSocketsInGroupForTesting(TestGroupId("a"))); // The other ConnectJob should have either completed, or still be connecting. EXPECT_EQ(1u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a")) + pool_->IdleSocketCountInGroup(TestGroupId("a"))); handle.Reset(); ASSERT_TRUE(pool_->HasGroupForTesting(TestGroupId("a"))); EXPECT_EQ(2u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a")) + pool_->IdleSocketCountInGroup(TestGroupId("a"))); EXPECT_EQ(0, pool_->NumActiveSocketsInGroupForTesting(TestGroupId("a"))); } TEST_F(ClientSocketPoolBaseTest, CancelRequest) { CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); EXPECT_THAT(StartRequest(TestGroupId("a"), DEFAULT_PRIORITY), IsOk()); EXPECT_THAT(StartRequest(TestGroupId("a"), DEFAULT_PRIORITY), IsOk()); EXPECT_THAT(StartRequest(TestGroupId("a"), LOWEST), IsError(ERR_IO_PENDING)); EXPECT_THAT(StartRequest(TestGroupId("a"), MEDIUM), IsError(ERR_IO_PENDING)); EXPECT_THAT(StartRequest(TestGroupId("a"), HIGHEST), IsError(ERR_IO_PENDING)); EXPECT_THAT(StartRequest(TestGroupId("a"), LOW), IsError(ERR_IO_PENDING)); EXPECT_THAT(StartRequest(TestGroupId("a"), LOWEST), IsError(ERR_IO_PENDING)); // Cancel a request. size_t index_to_cancel = kDefaultMaxSocketsPerGroup + 2; EXPECT_FALSE((*requests())[index_to_cancel]->handle()->is_initialized()); (*requests())[index_to_cancel]->handle()->Reset(); ReleaseAllConnections(ClientSocketPoolTest::KEEP_ALIVE); EXPECT_EQ(kDefaultMaxSocketsPerGroup, client_socket_factory_.allocation_count()); EXPECT_EQ(requests_size() - kDefaultMaxSocketsPerGroup - 1, completion_count()); EXPECT_EQ(1, GetOrderOfRequest(1)); EXPECT_EQ(2, GetOrderOfRequest(2)); EXPECT_EQ(5, GetOrderOfRequest(3)); EXPECT_EQ(3, GetOrderOfRequest(4)); EXPECT_EQ(ClientSocketPoolTest::kRequestNotFound, GetOrderOfRequest(5)); // Canceled request. EXPECT_EQ(4, GetOrderOfRequest(6)); EXPECT_EQ(6, GetOrderOfRequest(7)); // Make sure we test order of all requests made. EXPECT_EQ(ClientSocketPoolTest::kIndexOutOfBounds, GetOrderOfRequest(8)); } // Function to be used as a callback on socket request completion. It first // disconnects the successfully connected socket from the first request, and // then reuses the ClientSocketHandle to request another socket. // // |nested_callback| is called with the result of the second socket request. void RequestSocketOnComplete(ClientSocketHandle* handle, TransportClientSocketPool* pool, TestConnectJobFactory* test_connect_job_factory, TestConnectJob::JobType next_job_type, TestCompletionCallback* nested_callback, int first_request_result) { EXPECT_THAT(first_request_result, IsOk()); test_connect_job_factory->set_job_type(next_job_type); // Don't allow reuse of the socket. Disconnect it and then release it. if (handle->socket()) handle->socket()->Disconnect(); handle->Reset(); TestCompletionCallback callback; int rv = handle->Init( TestGroupId("a"), ClientSocketPool::SocketParams::CreateForHttpForTesting(), absl::nullopt, LOWEST, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, nested_callback->callback(), ClientSocketPool::ProxyAuthCallback(), pool, NetLogWithSource()); if (rv != ERR_IO_PENDING) { DCHECK_EQ(TestConnectJob::kMockJob, next_job_type); nested_callback->callback().Run(rv); } else { DCHECK_EQ(TestConnectJob::kMockPendingJob, next_job_type); } } // Tests the case where a second socket is requested in a completion callback, // and the second socket connects asynchronously. Reuses the same // ClientSocketHandle for the second socket, after disconnecting the first. TEST_F(ClientSocketPoolBaseTest, RequestPendingJobTwice) { CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob); ClientSocketHandle handle; TestCompletionCallback second_result_callback; int rv = handle.Init( TestGroupId("a"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, base::BindOnce(&RequestSocketOnComplete, &handle, pool_.get(), connect_job_factory_, TestConnectJob::kMockPendingJob, &second_result_callback), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource()); ASSERT_THAT(rv, IsError(ERR_IO_PENDING)); EXPECT_THAT(second_result_callback.WaitForResult(), IsOk()); } // Tests the case where a second socket is requested in a completion callback, // and the second socket connects synchronously. Reuses the same // ClientSocketHandle for the second socket, after disconnecting the first. TEST_F(ClientSocketPoolBaseTest, RequestPendingJobThenSynchronous) { CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob); ClientSocketHandle handle; TestCompletionCallback second_result_callback; int rv = handle.Init( TestGroupId("a"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, base::BindOnce(&RequestSocketOnComplete, &handle, pool_.get(), connect_job_factory_, TestConnectJob::kMockPendingJob, &second_result_callback), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource()); ASSERT_THAT(rv, IsError(ERR_IO_PENDING)); EXPECT_THAT(second_result_callback.WaitForResult(), IsOk()); } // Make sure that pending requests get serviced after active requests get // cancelled. TEST_F(ClientSocketPoolBaseTest, CancelActiveRequestWithPendingRequests) { CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob); EXPECT_THAT(StartRequest(TestGroupId("a"), DEFAULT_PRIORITY), IsError(ERR_IO_PENDING)); EXPECT_THAT(StartRequest(TestGroupId("a"), DEFAULT_PRIORITY), IsError(ERR_IO_PENDING)); EXPECT_THAT(StartRequest(TestGroupId("a"), DEFAULT_PRIORITY), IsError(ERR_IO_PENDING)); EXPECT_THAT(StartRequest(TestGroupId("a"), DEFAULT_PRIORITY), IsError(ERR_IO_PENDING)); EXPECT_THAT(StartRequest(TestGroupId("a"), DEFAULT_PRIORITY), IsError(ERR_IO_PENDING)); EXPECT_THAT(StartRequest(TestGroupId("a"), DEFAULT_PRIORITY), IsError(ERR_IO_PENDING)); EXPECT_THAT(StartRequest(TestGroupId("a"), DEFAULT_PRIORITY), IsError(ERR_IO_PENDING)); // Now, kDefaultMaxSocketsPerGroup requests should be active. // Let's cancel them. for (int i = 0; i < kDefaultMaxSocketsPerGroup; ++i) { ASSERT_FALSE(request(i)->handle()->is_initialized()); request(i)->handle()->Reset(); } // Let's wait for the rest to complete now. for (size_t i = kDefaultMaxSocketsPerGroup; i < requests_size(); ++i) { EXPECT_THAT(request(i)->WaitForResult(), IsOk()); request(i)->handle()->Reset(); } EXPECT_EQ(requests_size() - kDefaultMaxSocketsPerGroup, completion_count()); } // Make sure that pending requests get serviced after active requests fail. TEST_F(ClientSocketPoolBaseTest, FailingActiveRequestWithPendingRequests) { const size_t kMaxSockets = 5; CreatePool(kMaxSockets, kDefaultMaxSocketsPerGroup); connect_job_factory_->set_job_type(TestConnectJob::kMockPendingFailingJob); const size_t kNumberOfRequests = 2 * kDefaultMaxSocketsPerGroup + 1; ASSERT_LE(kNumberOfRequests, kMaxSockets); // Otherwise the test will hang. // Queue up all the requests for (size_t i = 0; i < kNumberOfRequests; ++i) EXPECT_THAT(StartRequest(TestGroupId("a"), DEFAULT_PRIORITY), IsError(ERR_IO_PENDING)); for (size_t i = 0; i < kNumberOfRequests; ++i) EXPECT_THAT(request(i)->WaitForResult(), IsError(ERR_CONNECTION_FAILED)); } // Make sure that pending requests that complete synchronously get serviced // after active requests fail. See https://crbug.com/723748 TEST_F(ClientSocketPoolBaseTest, HandleMultipleSyncFailuresAfterAsyncFailure) { const size_t kNumberOfRequests = 10; const size_t kMaxSockets = 1; CreatePool(kMaxSockets, kMaxSockets); connect_job_factory_->set_job_type(TestConnectJob::kMockPendingFailingJob); EXPECT_THAT(StartRequest(TestGroupId("a"), DEFAULT_PRIORITY), IsError(ERR_IO_PENDING)); connect_job_factory_->set_job_type(TestConnectJob::kMockFailingJob); // Queue up all the other requests for (size_t i = 1; i < kNumberOfRequests; ++i) EXPECT_THAT(StartRequest(TestGroupId("a"), DEFAULT_PRIORITY), IsError(ERR_IO_PENDING)); // Make sure all requests fail, instead of hanging. for (size_t i = 0; i < kNumberOfRequests; ++i) EXPECT_THAT(request(i)->WaitForResult(), IsError(ERR_CONNECTION_FAILED)); } TEST_F(ClientSocketPoolBaseTest, CancelActiveRequestThenRequestSocket) { CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob); ClientSocketHandle handle; TestCompletionCallback callback; int rv = handle.Init( TestGroupId("a"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource()); EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); // Cancel the active request. handle.Reset(); rv = handle.Init(TestGroupId("a"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource()); EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); EXPECT_THAT(callback.WaitForResult(), IsOk()); EXPECT_FALSE(handle.is_reused()); TestLoadTimingInfoConnectedNotReused(handle); EXPECT_EQ(2, client_socket_factory_.allocation_count()); } TEST_F(ClientSocketPoolBaseTest, CloseIdleSocketsForced) { const char kReason[] = "Really nifty reason"; CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); ClientSocketHandle handle; TestCompletionCallback callback; int rv = handle.Init(TestGroupId("a"), params_, absl::nullopt, LOWEST, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource::Make(NetLogSourceType::NONE)); EXPECT_THAT(rv, IsOk()); ASSERT_TRUE(handle.socket()); NetLogSource source = handle.socket()->NetLog().source(); handle.Reset(); EXPECT_EQ(1, pool_->IdleSocketCount()); pool_->CloseIdleSockets(kReason); ExpectSocketClosedWithReason(source, kReason); } TEST_F(ClientSocketPoolBaseTest, CloseIdleSocketsInGroupForced) { CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); TestCompletionCallback callback; NetLogWithSource net_log_with_source = NetLogWithSource::Make(NetLogSourceType::NONE); ClientSocketHandle handle1; int rv = handle1.Init( TestGroupId("a"), params_, absl::nullopt, LOWEST, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), net_log_with_source); EXPECT_THAT(rv, IsOk()); ClientSocketHandle handle2; rv = handle2.Init(TestGroupId("a"), params_, absl::nullopt, LOWEST, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), net_log_with_source); ClientSocketHandle handle3; rv = handle3.Init(TestGroupId("b"), params_, absl::nullopt, LOWEST, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), net_log_with_source); EXPECT_THAT(rv, IsOk()); handle1.Reset(); handle2.Reset(); handle3.Reset(); EXPECT_EQ(3, pool_->IdleSocketCount()); pool_->CloseIdleSocketsInGroup(TestGroupId("a"), "Very good reason"); EXPECT_EQ(1, pool_->IdleSocketCount()); } TEST_F(ClientSocketPoolBaseTest, CleanUpUnusableIdleSockets) { CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); ClientSocketHandle handle; TestCompletionCallback callback; NetLogWithSource net_log_with_source = NetLogWithSource::Make(NetLogSourceType::NONE); int rv = handle.Init( TestGroupId("a"), params_, absl::nullopt, LOWEST, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), net_log_with_source); EXPECT_THAT(rv, IsOk()); StreamSocket* socket = handle.socket(); ASSERT_TRUE(socket); handle.Reset(); EXPECT_EQ(1, pool_->IdleSocketCount()); // Disconnect socket now to make the socket unusable. NetLogSource source = socket->NetLog().source(); socket->Disconnect(); ClientSocketHandle handle2; rv = handle2.Init(TestGroupId("a"), params_, absl::nullopt, LOWEST, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), net_log_with_source); EXPECT_THAT(rv, IsOk()); EXPECT_FALSE(handle2.is_reused()); // This is admittedly not an accurate error in this case, but normally code // doesn't secretly keep a raw pointers to sockets returned to the socket pool // and close them out of band, so discovering an idle socket was closed when // trying to reuse it normally means it was closed by the remote side. ExpectSocketClosedWithReason( source, TransportClientSocketPool::kRemoteSideClosedConnection); } // Regression test for http://crbug.com/17985. TEST_F(ClientSocketPoolBaseTest, GroupWithPendingRequestsIsNotEmpty) { const int kMaxSockets = 3; const int kMaxSocketsPerGroup = 2; CreatePool(kMaxSockets, kMaxSocketsPerGroup); const RequestPriority kHighPriority = HIGHEST; EXPECT_THAT(StartRequest(TestGroupId("a"), DEFAULT_PRIORITY), IsOk()); EXPECT_THAT(StartRequest(TestGroupId("a"), DEFAULT_PRIORITY), IsOk()); // This is going to be a pending request in an otherwise empty group. EXPECT_THAT(StartRequest(TestGroupId("a"), DEFAULT_PRIORITY), IsError(ERR_IO_PENDING)); // Reach the maximum socket limit. EXPECT_THAT(StartRequest(TestGroupId("b"), DEFAULT_PRIORITY), IsOk()); // Create a stalled group with high priorities. EXPECT_THAT(StartRequest(TestGroupId("c"), kHighPriority), IsError(ERR_IO_PENDING)); EXPECT_THAT(StartRequest(TestGroupId("c"), kHighPriority), IsError(ERR_IO_PENDING)); // Release the first two sockets from TestGroupId("a"). Because this is a // keepalive, the first release will unblock the pending request for // TestGroupId("a"). The second release will unblock a request for "c", // because it is the next high priority socket. EXPECT_TRUE(ReleaseOneConnection(ClientSocketPoolTest::KEEP_ALIVE)); EXPECT_TRUE(ReleaseOneConnection(ClientSocketPoolTest::KEEP_ALIVE)); // Closing idle sockets should not get us into trouble, but in the bug // we were hitting a CHECK here. EXPECT_EQ(0u, pool_->IdleSocketCountInGroup(TestGroupId("a"))); pool_->CloseIdleSockets("Very good reason"); // Run the released socket wakeups. base::RunLoop().RunUntilIdle(); } TEST_F(ClientSocketPoolBaseTest, BasicAsynchronous) { CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob); ClientSocketHandle handle; TestCompletionCallback callback; NetLogWithSource net_log_with_source = NetLogWithSource::Make(NetLogSourceType::NONE); int rv = handle.Init( TestGroupId("a"), params_, absl::nullopt, LOWEST, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), net_log_with_source); EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); EXPECT_EQ(LOAD_STATE_CONNECTING, pool_->GetLoadState(TestGroupId("a"), &handle)); TestLoadTimingInfoNotConnected(handle); EXPECT_THAT(callback.WaitForResult(), IsOk()); EXPECT_TRUE(handle.is_initialized()); EXPECT_TRUE(handle.socket()); TestLoadTimingInfoConnectedNotReused(handle); handle.Reset(); TestLoadTimingInfoNotConnected(handle); auto entries = net_log_observer_.GetEntriesForSource(net_log_with_source.source()); EXPECT_EQ(5u, entries.size()); EXPECT_TRUE(LogContainsEvent( entries, 0, NetLogEventType::TCP_CLIENT_SOCKET_POOL_REQUESTED_SOCKET, NetLogEventPhase::NONE)); EXPECT_TRUE(LogContainsBeginEvent(entries, 1, NetLogEventType::SOCKET_POOL)); EXPECT_TRUE(LogContainsEvent( entries, 2, NetLogEventType::SOCKET_POOL_BOUND_TO_CONNECT_JOB, NetLogEventPhase::NONE)); EXPECT_TRUE(LogContainsEvent(entries, 3, NetLogEventType::SOCKET_POOL_BOUND_TO_SOCKET, NetLogEventPhase::NONE)); EXPECT_TRUE(LogContainsEndEvent(entries, 4, NetLogEventType::SOCKET_POOL)); } TEST_F(ClientSocketPoolBaseTest, InitConnectionAsynchronousFailure) { CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); connect_job_factory_->set_job_type(TestConnectJob::kMockPendingFailingJob); ClientSocketHandle handle; TestCompletionCallback callback; NetLogWithSource net_log_with_source = NetLogWithSource::Make(NetLogSourceType::NONE); // Set the additional error state members to ensure that they get cleared. handle.set_is_ssl_error(true); handle.set_ssl_cert_request_info(base::MakeRefCounted()); EXPECT_EQ( ERR_IO_PENDING, handle.Init(TestGroupId("a"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), net_log_with_source)); EXPECT_EQ(LOAD_STATE_CONNECTING, pool_->GetLoadState(TestGroupId("a"), &handle)); EXPECT_THAT(callback.WaitForResult(), IsError(ERR_CONNECTION_FAILED)); EXPECT_FALSE(handle.is_ssl_error()); EXPECT_FALSE(handle.ssl_cert_request_info()); auto entries = net_log_observer_.GetEntriesForSource(net_log_with_source.source()); EXPECT_EQ(4u, entries.size()); EXPECT_TRUE(LogContainsEvent( entries, 0, NetLogEventType::TCP_CLIENT_SOCKET_POOL_REQUESTED_SOCKET, NetLogEventPhase::NONE)); EXPECT_TRUE(LogContainsBeginEvent(entries, 1, NetLogEventType::SOCKET_POOL)); EXPECT_TRUE(LogContainsEvent( entries, 2, NetLogEventType::SOCKET_POOL_BOUND_TO_CONNECT_JOB, NetLogEventPhase::NONE)); EXPECT_TRUE(LogContainsEndEvent(entries, 3, NetLogEventType::SOCKET_POOL)); } // Check that an async ConnectJob failure does not result in creation of a new // ConnectJob when there's another pending request also waiting on its own // ConnectJob. See http://crbug.com/463960. TEST_F(ClientSocketPoolBaseTest, AsyncFailureWithPendingRequestWithJob) { CreatePool(2, 2); connect_job_factory_->set_job_type(TestConnectJob::kMockPendingFailingJob); EXPECT_THAT(StartRequest(TestGroupId("a"), DEFAULT_PRIORITY), IsError(ERR_IO_PENDING)); EXPECT_THAT(StartRequest(TestGroupId("a"), DEFAULT_PRIORITY), IsError(ERR_IO_PENDING)); EXPECT_THAT(request(0)->WaitForResult(), IsError(ERR_CONNECTION_FAILED)); EXPECT_THAT(request(1)->WaitForResult(), IsError(ERR_CONNECTION_FAILED)); EXPECT_EQ(2, client_socket_factory_.allocation_count()); } TEST_F(ClientSocketPoolBaseTest, TwoRequestsCancelOne) { // TODO(eroman): Add back the log expectations! Removed them because the // ordering is difficult, and some may fire during destructor. CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob); ClientSocketHandle handle; TestCompletionCallback callback; ClientSocketHandle handle2; TestCompletionCallback callback2; EXPECT_EQ( ERR_IO_PENDING, handle.Init(TestGroupId("a"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); RecordingNetLogObserver log2; EXPECT_EQ( ERR_IO_PENDING, handle2.Init(TestGroupId("a"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback2.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); handle.Reset(); // At this point, request 2 is just waiting for the connect job to finish. EXPECT_THAT(callback2.WaitForResult(), IsOk()); handle2.Reset(); // Now request 2 has actually finished. // TODO(eroman): Add back log expectations. } TEST_F(ClientSocketPoolBaseTest, CancelRequestLimitsJobs) { CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob); EXPECT_THAT(StartRequest(TestGroupId("a"), LOWEST), IsError(ERR_IO_PENDING)); EXPECT_THAT(StartRequest(TestGroupId("a"), LOW), IsError(ERR_IO_PENDING)); EXPECT_THAT(StartRequest(TestGroupId("a"), MEDIUM), IsError(ERR_IO_PENDING)); EXPECT_THAT(StartRequest(TestGroupId("a"), HIGHEST), IsError(ERR_IO_PENDING)); EXPECT_EQ(kDefaultMaxSocketsPerGroup, static_cast( pool_->NumConnectJobsInGroupForTesting(TestGroupId("a")))); (*requests())[2]->handle()->Reset(); (*requests())[3]->handle()->Reset(); EXPECT_EQ(kDefaultMaxSocketsPerGroup, static_cast( pool_->NumConnectJobsInGroupForTesting(TestGroupId("a")))); (*requests())[1]->handle()->Reset(); EXPECT_EQ(kDefaultMaxSocketsPerGroup, static_cast( pool_->NumConnectJobsInGroupForTesting(TestGroupId("a")))); (*requests())[0]->handle()->Reset(); EXPECT_EQ(kDefaultMaxSocketsPerGroup, static_cast( pool_->NumConnectJobsInGroupForTesting(TestGroupId("a")))); } // When requests and ConnectJobs are not coupled, the request will get serviced // by whatever comes first. TEST_F(ClientSocketPoolBaseTest, ReleaseSockets) { CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); // Start job 1 (async OK) connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob); std::vector request_order; size_t completion_count; // unused TestSocketRequest req1(&request_order, &completion_count); int rv = req1.handle()->Init( TestGroupId("a"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, req1.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource()); EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); EXPECT_THAT(req1.WaitForResult(), IsOk()); // Job 1 finished OK. Start job 2 (also async OK). Request 3 is pending // without a job. connect_job_factory_->set_job_type(TestConnectJob::kMockWaitingJob); TestSocketRequest req2(&request_order, &completion_count); rv = req2.handle()->Init( TestGroupId("a"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, req2.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource()); EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); TestSocketRequest req3(&request_order, &completion_count); rv = req3.handle()->Init( TestGroupId("a"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, req3.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource()); EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); // Both Requests 2 and 3 are pending. We release socket 1 which should // service request 2. Request 3 should still be waiting. req1.handle()->Reset(); // Run the released socket wakeups. base::RunLoop().RunUntilIdle(); ASSERT_TRUE(req2.handle()->socket()); EXPECT_THAT(req2.WaitForResult(), IsOk()); EXPECT_FALSE(req3.handle()->socket()); // Signal job 2, which should service request 3. client_socket_factory_.SignalJobs(); EXPECT_THAT(req3.WaitForResult(), IsOk()); ASSERT_EQ(3u, request_order.size()); EXPECT_EQ(&req1, request_order[0]); EXPECT_EQ(&req2, request_order[1]); EXPECT_EQ(&req3, request_order[2]); EXPECT_EQ(0u, pool_->IdleSocketCountInGroup(TestGroupId("a"))); } // The requests are not coupled to the jobs. So, the requests should finish in // their priority / insertion order. TEST_F(ClientSocketPoolBaseTest, PendingJobCompletionOrder) { CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); // First two jobs are async. connect_job_factory_->set_job_type(TestConnectJob::kMockPendingFailingJob); std::vector request_order; size_t completion_count; // unused TestSocketRequest req1(&request_order, &completion_count); int rv = req1.handle()->Init( TestGroupId("a"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, req1.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource()); EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); TestSocketRequest req2(&request_order, &completion_count); rv = req2.handle()->Init( TestGroupId("a"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, req2.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource()); EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); // The pending job is sync. connect_job_factory_->set_job_type(TestConnectJob::kMockJob); TestSocketRequest req3(&request_order, &completion_count); rv = req3.handle()->Init( TestGroupId("a"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, req3.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource()); EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); EXPECT_THAT(req1.WaitForResult(), IsError(ERR_CONNECTION_FAILED)); EXPECT_THAT(req2.WaitForResult(), IsOk()); EXPECT_THAT(req3.WaitForResult(), IsError(ERR_CONNECTION_FAILED)); ASSERT_EQ(3u, request_order.size()); EXPECT_EQ(&req1, request_order[0]); EXPECT_EQ(&req2, request_order[1]); EXPECT_EQ(&req3, request_order[2]); } // Test GetLoadState in the case there's only one socket request. TEST_F(ClientSocketPoolBaseTest, LoadStateOneRequest) { CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); connect_job_factory_->set_job_type(TestConnectJob::kMockWaitingJob); ClientSocketHandle handle; TestCompletionCallback callback; int rv = handle.Init( TestGroupId("a"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource()); EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); EXPECT_EQ(LOAD_STATE_CONNECTING, handle.GetLoadState()); client_socket_factory_.SetJobLoadState(0, LOAD_STATE_SSL_HANDSHAKE); EXPECT_EQ(LOAD_STATE_SSL_HANDSHAKE, handle.GetLoadState()); // No point in completing the connection, since ClientSocketHandles only // expect the LoadState to be checked while connecting. } // Test GetLoadState in the case there are two socket requests. TEST_F(ClientSocketPoolBaseTest, LoadStateTwoRequests) { CreatePool(2, 2); connect_job_factory_->set_job_type(TestConnectJob::kMockWaitingJob); ClientSocketHandle handle; TestCompletionCallback callback; int rv = handle.Init( TestGroupId("a"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource()); EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); client_socket_factory_.SetJobLoadState(0, LOAD_STATE_RESOLVING_HOST); ClientSocketHandle handle2; TestCompletionCallback callback2; rv = handle2.Init(TestGroupId("a"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback2.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource()); EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); client_socket_factory_.SetJobLoadState(1, LOAD_STATE_RESOLVING_HOST); // Each handle should reflect the state of its own job. EXPECT_EQ(LOAD_STATE_RESOLVING_HOST, handle.GetLoadState()); EXPECT_EQ(LOAD_STATE_RESOLVING_HOST, handle2.GetLoadState()); // Update the state of the first job. client_socket_factory_.SetJobLoadState(0, LOAD_STATE_CONNECTING); // Only the state of the first request should have changed. EXPECT_EQ(LOAD_STATE_CONNECTING, handle.GetLoadState()); EXPECT_EQ(LOAD_STATE_RESOLVING_HOST, handle2.GetLoadState()); // Update the state of the second job. client_socket_factory_.SetJobLoadState(1, LOAD_STATE_SSL_HANDSHAKE); // Only the state of the second request should have changed. EXPECT_EQ(LOAD_STATE_CONNECTING, handle.GetLoadState()); EXPECT_EQ(LOAD_STATE_SSL_HANDSHAKE, handle2.GetLoadState()); // Second job connects and the first request gets the socket. The // second handle switches to the state of the remaining ConnectJob. client_socket_factory_.SignalJob(1); EXPECT_THAT(callback.WaitForResult(), IsOk()); EXPECT_EQ(LOAD_STATE_CONNECTING, handle2.GetLoadState()); } // Test GetLoadState in the case the per-group limit is reached. TEST_F(ClientSocketPoolBaseTest, LoadStateGroupLimit) { CreatePool(2, 1); connect_job_factory_->set_job_type(TestConnectJob::kMockWaitingJob); ClientSocketHandle handle; TestCompletionCallback callback; int rv = handle.Init( TestGroupId("a"), params_, absl::nullopt, MEDIUM, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource()); EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); EXPECT_EQ(LOAD_STATE_CONNECTING, handle.GetLoadState()); // Request another socket from the same pool, buth with a higher priority. // The first request should now be stalled at the socket group limit. ClientSocketHandle handle2; TestCompletionCallback callback2; rv = handle2.Init(TestGroupId("a"), params_, absl::nullopt, HIGHEST, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback2.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource()); EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); EXPECT_EQ(LOAD_STATE_WAITING_FOR_AVAILABLE_SOCKET, handle.GetLoadState()); EXPECT_EQ(LOAD_STATE_CONNECTING, handle2.GetLoadState()); // The first handle should remain stalled as the other socket goes through // the connect process. client_socket_factory_.SetJobLoadState(0, LOAD_STATE_SSL_HANDSHAKE); EXPECT_EQ(LOAD_STATE_WAITING_FOR_AVAILABLE_SOCKET, handle.GetLoadState()); EXPECT_EQ(LOAD_STATE_SSL_HANDSHAKE, handle2.GetLoadState()); client_socket_factory_.SignalJob(0); EXPECT_THAT(callback2.WaitForResult(), IsOk()); EXPECT_EQ(LOAD_STATE_WAITING_FOR_AVAILABLE_SOCKET, handle.GetLoadState()); // Closing the second socket should cause the stalled handle to finally get a // ConnectJob. handle2.socket()->Disconnect(); handle2.Reset(); EXPECT_EQ(LOAD_STATE_CONNECTING, handle.GetLoadState()); } // Test GetLoadState in the case the per-pool limit is reached. TEST_F(ClientSocketPoolBaseTest, LoadStatePoolLimit) { CreatePool(2, 2); connect_job_factory_->set_job_type(TestConnectJob::kMockWaitingJob); ClientSocketHandle handle; TestCompletionCallback callback; int rv = handle.Init( TestGroupId("a"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource()); EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); // Request for socket from another pool. ClientSocketHandle handle2; TestCompletionCallback callback2; rv = handle2.Init(TestGroupId("b"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback2.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource()); EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); // Request another socket from the first pool. Request should stall at the // socket pool limit. ClientSocketHandle handle3; TestCompletionCallback callback3; rv = handle3.Init(TestGroupId("a"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback2.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource()); EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); // The third handle should remain stalled as the other sockets in its group // goes through the connect process. EXPECT_EQ(LOAD_STATE_CONNECTING, handle.GetLoadState()); EXPECT_EQ(LOAD_STATE_WAITING_FOR_STALLED_SOCKET_POOL, handle3.GetLoadState()); client_socket_factory_.SetJobLoadState(0, LOAD_STATE_SSL_HANDSHAKE); EXPECT_EQ(LOAD_STATE_SSL_HANDSHAKE, handle.GetLoadState()); EXPECT_EQ(LOAD_STATE_WAITING_FOR_STALLED_SOCKET_POOL, handle3.GetLoadState()); client_socket_factory_.SignalJob(0); EXPECT_THAT(callback.WaitForResult(), IsOk()); EXPECT_EQ(LOAD_STATE_WAITING_FOR_STALLED_SOCKET_POOL, handle3.GetLoadState()); // Closing a socket should allow the stalled handle to finally get a new // ConnectJob. handle.socket()->Disconnect(); handle.Reset(); EXPECT_EQ(LOAD_STATE_CONNECTING, handle3.GetLoadState()); } TEST_F(ClientSocketPoolBaseTest, CertError) { CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); connect_job_factory_->set_job_type(TestConnectJob::kMockCertErrorJob); ClientSocketHandle handle; TestCompletionCallback callback; EXPECT_EQ( ERR_CERT_COMMON_NAME_INVALID, handle.Init(TestGroupId("a"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); EXPECT_TRUE(handle.is_initialized()); EXPECT_TRUE(handle.socket()); } TEST_F(ClientSocketPoolBaseTest, AsyncCertError) { CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); connect_job_factory_->set_job_type(TestConnectJob::kMockPendingCertErrorJob); ClientSocketHandle handle; TestCompletionCallback callback; EXPECT_EQ( ERR_IO_PENDING, handle.Init(TestGroupId("a"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); EXPECT_EQ(LOAD_STATE_CONNECTING, pool_->GetLoadState(TestGroupId("a"), &handle)); EXPECT_THAT(callback.WaitForResult(), IsError(ERR_CERT_COMMON_NAME_INVALID)); EXPECT_TRUE(handle.is_initialized()); EXPECT_TRUE(handle.socket()); } TEST_F(ClientSocketPoolBaseTest, AdditionalErrorStateSynchronous) { CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); connect_job_factory_->set_job_type( TestConnectJob::kMockAdditionalErrorStateJob); ClientSocketHandle handle; TestCompletionCallback callback; EXPECT_EQ( ERR_CONNECTION_FAILED, handle.Init(TestGroupId("a"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); EXPECT_FALSE(handle.is_initialized()); EXPECT_FALSE(handle.socket()); EXPECT_TRUE(handle.is_ssl_error()); EXPECT_TRUE(handle.ssl_cert_request_info()); } TEST_F(ClientSocketPoolBaseTest, AdditionalErrorStateAsynchronous) { CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); connect_job_factory_->set_job_type( TestConnectJob::kMockPendingAdditionalErrorStateJob); ClientSocketHandle handle; TestCompletionCallback callback; EXPECT_EQ( ERR_IO_PENDING, handle.Init(TestGroupId("a"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); EXPECT_EQ(LOAD_STATE_CONNECTING, pool_->GetLoadState(TestGroupId("a"), &handle)); EXPECT_THAT(callback.WaitForResult(), IsError(ERR_CONNECTION_FAILED)); EXPECT_FALSE(handle.is_initialized()); EXPECT_FALSE(handle.socket()); EXPECT_TRUE(handle.is_ssl_error()); EXPECT_TRUE(handle.ssl_cert_request_info()); } // Make sure we can reuse sockets. TEST_F(ClientSocketPoolBaseTest, CleanupTimedOutIdleSocketsReuse) { CreatePoolWithIdleTimeouts( kDefaultMaxSockets, kDefaultMaxSocketsPerGroup, base::TimeDelta(), // Time out unused sockets immediately. base::Days(1)); // Don't time out used sockets. connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob); ClientSocketHandle handle; TestCompletionCallback callback; int rv = handle.Init( TestGroupId("a"), params_, absl::nullopt, LOWEST, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource()); ASSERT_THAT(rv, IsError(ERR_IO_PENDING)); EXPECT_EQ(LOAD_STATE_CONNECTING, pool_->GetLoadState(TestGroupId("a"), &handle)); ASSERT_THAT(callback.WaitForResult(), IsOk()); // Use and release the socket. EXPECT_EQ(1, handle.socket()->Write(nullptr, 1, CompletionOnceCallback(), TRAFFIC_ANNOTATION_FOR_TESTS)); TestLoadTimingInfoConnectedNotReused(handle); handle.Reset(); // Should now have one idle socket. ASSERT_EQ(1, pool_->IdleSocketCount()); // Request a new socket. This should reuse the old socket and complete // synchronously. NetLogWithSource net_log_with_source = NetLogWithSource::Make(NetLogSourceType::NONE); rv = handle.Init( TestGroupId("a"), params_, absl::nullopt, LOWEST, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, CompletionOnceCallback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), net_log_with_source); ASSERT_THAT(rv, IsOk()); EXPECT_TRUE(handle.is_reused()); TestLoadTimingInfoConnectedReused(handle); ASSERT_TRUE(pool_->HasGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->IdleSocketCountInGroup(TestGroupId("a"))); EXPECT_EQ(1, pool_->NumActiveSocketsInGroupForTesting(TestGroupId("a"))); auto entries = net_log_observer_.GetEntriesForSource(net_log_with_source.source()); EXPECT_TRUE(LogContainsEvent( entries, 0, NetLogEventType::TCP_CLIENT_SOCKET_POOL_REQUESTED_SOCKET, NetLogEventPhase::NONE)); EXPECT_TRUE(LogContainsBeginEvent(entries, 1, NetLogEventType::SOCKET_POOL)); EXPECT_TRUE(LogContainsEntryWithType( entries, 2, NetLogEventType::SOCKET_POOL_REUSED_AN_EXISTING_SOCKET)); } // Make sure we cleanup old unused sockets. TEST_F(ClientSocketPoolBaseTest, CleanupTimedOutIdleSocketsNoReuse) { CreatePoolWithIdleTimeouts( kDefaultMaxSockets, kDefaultMaxSocketsPerGroup, base::TimeDelta(), // Time out unused sockets immediately base::TimeDelta()); // Time out used sockets immediately connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob); // Startup two mock pending connect jobs, which will sit in the MessageLoop. ClientSocketHandle handle; TestCompletionCallback callback; int rv = handle.Init( TestGroupId("a"), params_, absl::nullopt, LOWEST, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource()); ASSERT_THAT(rv, IsError(ERR_IO_PENDING)); EXPECT_EQ(LOAD_STATE_CONNECTING, pool_->GetLoadState(TestGroupId("a"), &handle)); ClientSocketHandle handle2; TestCompletionCallback callback2; rv = handle2.Init(TestGroupId("a"), params_, absl::nullopt, LOWEST, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback2.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource()); ASSERT_THAT(rv, IsError(ERR_IO_PENDING)); EXPECT_EQ(LOAD_STATE_CONNECTING, pool_->GetLoadState(TestGroupId("a"), &handle2)); // Cancel one of the requests. Wait for the other, which will get the first // job. Release the socket. Run the loop again to make sure the second // socket is sitting idle and the first one is released (since ReleaseSocket() // just posts a DoReleaseSocket() task). handle.Reset(); ASSERT_THAT(callback2.WaitForResult(), IsOk()); // Get the NetLogSource for the socket, so the time out reason can be checked // at the end of the test. NetLogSource net_log_source2 = handle2.socket()->NetLog().source(); // Use the socket. EXPECT_EQ(1, handle2.socket()->Write(nullptr, 1, CompletionOnceCallback(), TRAFFIC_ANNOTATION_FOR_TESTS)); handle2.Reset(); // We post all of our delayed tasks with a 2ms delay. I.e. they don't // actually become pending until 2ms after they have been created. In order // to flush all tasks, we need to wait so that we know there are no // soon-to-be-pending tasks waiting. FastForwardBy(base::Milliseconds(10)); // Both sockets should now be idle. ASSERT_EQ(2, pool_->IdleSocketCount()); // Request a new socket. This should cleanup the unused and timed out ones. // A new socket will be created rather than reusing the idle one. NetLogWithSource net_log_with_source = NetLogWithSource::Make(NetLogSourceType::NONE); TestCompletionCallback callback3; rv = handle.Init(TestGroupId("a"), params_, absl::nullopt, LOWEST, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback3.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), net_log_with_source); ASSERT_THAT(rv, IsError(ERR_IO_PENDING)); ASSERT_THAT(callback3.WaitForResult(), IsOk()); EXPECT_FALSE(handle.is_reused()); // Make sure the idle socket is closed. ASSERT_TRUE(pool_->HasGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->IdleSocketCountInGroup(TestGroupId("a"))); EXPECT_EQ(1, pool_->NumActiveSocketsInGroupForTesting(TestGroupId("a"))); auto entries = net_log_observer_.GetEntriesForSource(net_log_with_source.source()); EXPECT_FALSE(LogContainsEntryWithType( entries, 1, NetLogEventType::SOCKET_POOL_REUSED_AN_EXISTING_SOCKET)); ExpectSocketClosedWithReason( net_log_source2, TransportClientSocketPool::kIdleTimeLimitExpired); } // Make sure that we process all pending requests even when we're stalling // because of multiple releasing disconnected sockets. TEST_F(ClientSocketPoolBaseTest, MultipleReleasingDisconnectedSockets) { CreatePoolWithIdleTimeouts( kDefaultMaxSockets, kDefaultMaxSocketsPerGroup, base::TimeDelta(), // Time out unused sockets immediately. base::Days(1)); // Don't time out used sockets. connect_job_factory_->set_job_type(TestConnectJob::kMockJob); // Startup 4 connect jobs. Two of them will be pending. ClientSocketHandle handle; TestCompletionCallback callback; int rv = handle.Init( TestGroupId("a"), params_, absl::nullopt, LOWEST, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource()); EXPECT_THAT(rv, IsOk()); ClientSocketHandle handle2; TestCompletionCallback callback2; rv = handle2.Init(TestGroupId("a"), params_, absl::nullopt, LOWEST, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback2.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource()); EXPECT_THAT(rv, IsOk()); ClientSocketHandle handle3; TestCompletionCallback callback3; rv = handle3.Init(TestGroupId("a"), params_, absl::nullopt, LOWEST, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback3.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource()); EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); ClientSocketHandle handle4; TestCompletionCallback callback4; rv = handle4.Init(TestGroupId("a"), params_, absl::nullopt, LOWEST, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback4.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource()); EXPECT_THAT(rv, IsError(ERR_IO_PENDING)); // Release two disconnected sockets. handle.socket()->Disconnect(); handle.Reset(); handle2.socket()->Disconnect(); handle2.Reset(); EXPECT_THAT(callback3.WaitForResult(), IsOk()); EXPECT_FALSE(handle3.is_reused()); EXPECT_THAT(callback4.WaitForResult(), IsOk()); EXPECT_FALSE(handle4.is_reused()); } // Regression test for http://crbug.com/42267. // When DoReleaseSocket() is processed for one socket, it is blocked because the // other stalled groups all have releasing sockets, so no progress can be made. TEST_F(ClientSocketPoolBaseTest, SocketLimitReleasingSockets) { CreatePoolWithIdleTimeouts( 4 /* socket limit */, 4 /* socket limit per group */, base::TimeDelta(), // Time out unused sockets immediately. base::Days(1)); // Don't time out used sockets. connect_job_factory_->set_job_type(TestConnectJob::kMockJob); // Max out the socket limit with 2 per group. ClientSocketHandle handle_a[4]; TestCompletionCallback callback_a[4]; ClientSocketHandle handle_b[4]; TestCompletionCallback callback_b[4]; for (int i = 0; i < 2; ++i) { EXPECT_EQ(OK, handle_a[i].Init(TestGroupId("a"), params_, absl::nullopt, LOWEST, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback_a[i].callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); EXPECT_EQ(OK, handle_b[i].Init(TestGroupId("b"), params_, absl::nullopt, LOWEST, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback_b[i].callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); } // Make 4 pending requests, 2 per group. for (int i = 2; i < 4; ++i) { EXPECT_EQ( ERR_IO_PENDING, handle_a[i].Init(TestGroupId("a"), params_, absl::nullopt, LOWEST, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback_a[i].callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); EXPECT_EQ( ERR_IO_PENDING, handle_b[i].Init(TestGroupId("b"), params_, absl::nullopt, LOWEST, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback_b[i].callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); } // Release b's socket first. The order is important, because in // DoReleaseSocket(), we'll process b's released socket, and since both b and // a are stalled, but 'a' is lower lexicographically, we'll process group 'a' // first, which has a releasing socket, so it refuses to start up another // ConnectJob. So, we used to infinite loop on this. handle_b[0].socket()->Disconnect(); handle_b[0].Reset(); handle_a[0].socket()->Disconnect(); handle_a[0].Reset(); // Used to get stuck here. base::RunLoop().RunUntilIdle(); handle_b[1].socket()->Disconnect(); handle_b[1].Reset(); handle_a[1].socket()->Disconnect(); handle_a[1].Reset(); for (int i = 2; i < 4; ++i) { EXPECT_THAT(callback_b[i].WaitForResult(), IsOk()); EXPECT_THAT(callback_a[i].WaitForResult(), IsOk()); } } TEST_F(ClientSocketPoolBaseTest, ReleasingDisconnectedSocketsMaintainsPriorityOrder) { CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob); EXPECT_THAT(StartRequest(TestGroupId("a"), DEFAULT_PRIORITY), IsError(ERR_IO_PENDING)); EXPECT_THAT(StartRequest(TestGroupId("a"), DEFAULT_PRIORITY), IsError(ERR_IO_PENDING)); EXPECT_THAT(StartRequest(TestGroupId("a"), DEFAULT_PRIORITY), IsError(ERR_IO_PENDING)); EXPECT_THAT(StartRequest(TestGroupId("a"), DEFAULT_PRIORITY), IsError(ERR_IO_PENDING)); EXPECT_THAT((*requests())[0]->WaitForResult(), IsOk()); EXPECT_THAT((*requests())[1]->WaitForResult(), IsOk()); EXPECT_EQ(2u, completion_count()); // Releases one connection. EXPECT_TRUE(ReleaseOneConnection(ClientSocketPoolTest::NO_KEEP_ALIVE)); EXPECT_THAT((*requests())[2]->WaitForResult(), IsOk()); EXPECT_TRUE(ReleaseOneConnection(ClientSocketPoolTest::NO_KEEP_ALIVE)); EXPECT_THAT((*requests())[3]->WaitForResult(), IsOk()); EXPECT_EQ(4u, completion_count()); EXPECT_EQ(1, GetOrderOfRequest(1)); EXPECT_EQ(2, GetOrderOfRequest(2)); EXPECT_EQ(3, GetOrderOfRequest(3)); EXPECT_EQ(4, GetOrderOfRequest(4)); // Make sure we test order of all requests made. EXPECT_EQ(ClientSocketPoolTest::kIndexOutOfBounds, GetOrderOfRequest(5)); } class TestReleasingSocketRequest : public TestCompletionCallbackBase { public: TestReleasingSocketRequest(TransportClientSocketPool* pool, int expected_result, bool reset_releasing_handle) : pool_(pool), expected_result_(expected_result), reset_releasing_handle_(reset_releasing_handle) {} ~TestReleasingSocketRequest() override = default; ClientSocketHandle* handle() { return &handle_; } CompletionOnceCallback callback() { return base::BindOnce(&TestReleasingSocketRequest::OnComplete, base::Unretained(this)); } private: void OnComplete(int result) { SetResult(result); if (reset_releasing_handle_) handle_.Reset(); EXPECT_EQ( expected_result_, handle2_.Init( TestGroupId("a"), ClientSocketPool::SocketParams::CreateForHttpForTesting(), absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, CompletionOnceCallback(), ClientSocketPool::ProxyAuthCallback(), pool_, NetLogWithSource())); } const raw_ptr pool_; int expected_result_; bool reset_releasing_handle_; ClientSocketHandle handle_; ClientSocketHandle handle2_; }; TEST_F(ClientSocketPoolBaseTest, AdditionalErrorSocketsDontUseSlot) { CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); EXPECT_THAT(StartRequest(TestGroupId("b"), DEFAULT_PRIORITY), IsOk()); EXPECT_THAT(StartRequest(TestGroupId("a"), DEFAULT_PRIORITY), IsOk()); EXPECT_THAT(StartRequest(TestGroupId("b"), DEFAULT_PRIORITY), IsOk()); EXPECT_EQ(static_cast(requests_size()), client_socket_factory_.allocation_count()); connect_job_factory_->set_job_type( TestConnectJob::kMockPendingAdditionalErrorStateJob); TestReleasingSocketRequest req(pool_.get(), OK, false); EXPECT_EQ(ERR_IO_PENDING, req.handle()->Init( TestGroupId("a"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, req.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); // The next job should complete synchronously connect_job_factory_->set_job_type(TestConnectJob::kMockJob); EXPECT_THAT(req.WaitForResult(), IsError(ERR_CONNECTION_FAILED)); EXPECT_FALSE(req.handle()->is_initialized()); EXPECT_FALSE(req.handle()->socket()); EXPECT_TRUE(req.handle()->is_ssl_error()); EXPECT_TRUE(req.handle()->ssl_cert_request_info()); } // http://crbug.com/44724 regression test. // We start releasing the pool when we flush on network change. When that // happens, the only active references are in the ClientSocketHandles. When a // ConnectJob completes and calls back into the last ClientSocketHandle, that // callback can release the last reference and delete the pool. After the // callback finishes, we go back to the stack frame within the now-deleted pool. // Executing any code that refers to members of the now-deleted pool can cause // crashes. TEST_F(ClientSocketPoolBaseTest, CallbackThatReleasesPool) { CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); connect_job_factory_->set_job_type(TestConnectJob::kMockPendingFailingJob); ClientSocketHandle handle; TestCompletionCallback callback; EXPECT_EQ( ERR_IO_PENDING, handle.Init(TestGroupId("a"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); pool_->FlushWithError(ERR_NETWORK_CHANGED, "Network changed"); // We'll call back into this now. callback.WaitForResult(); } TEST_F(ClientSocketPoolBaseTest, DoNotReuseSocketAfterFlush) { CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob); ClientSocketHandle handle; TestCompletionCallback callback; EXPECT_EQ( ERR_IO_PENDING, handle.Init(TestGroupId("a"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); EXPECT_THAT(callback.WaitForResult(), IsOk()); EXPECT_EQ(ClientSocketHandle::UNUSED, handle.reuse_type()); NetLogSource source = handle.socket()->NetLog().source(); pool_->FlushWithError(ERR_NETWORK_CHANGED, "Network changed"); handle.Reset(); base::RunLoop().RunUntilIdle(); EXPECT_EQ( ERR_IO_PENDING, handle.Init(TestGroupId("a"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); EXPECT_THAT(callback.WaitForResult(), IsOk()); EXPECT_EQ(ClientSocketHandle::UNUSED, handle.reuse_type()); ExpectSocketClosedWithReason( source, TransportClientSocketPool::kSocketGenerationOutOfDate); } class ConnectWithinCallback : public TestCompletionCallbackBase { public: ConnectWithinCallback( const ClientSocketPool::GroupId& group_id, const scoped_refptr& params, TransportClientSocketPool* pool) : group_id_(group_id), params_(params), pool_(pool) {} ConnectWithinCallback(const ConnectWithinCallback&) = delete; ConnectWithinCallback& operator=(const ConnectWithinCallback&) = delete; ~ConnectWithinCallback() override = default; int WaitForNestedResult() { return nested_callback_.WaitForResult(); } CompletionOnceCallback callback() { return base::BindOnce(&ConnectWithinCallback::OnComplete, base::Unretained(this)); } private: void OnComplete(int result) { SetResult(result); EXPECT_EQ( ERR_IO_PENDING, handle_.Init(group_id_, params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, nested_callback_.callback(), ClientSocketPool::ProxyAuthCallback(), pool_, NetLogWithSource())); } const ClientSocketPool::GroupId group_id_; const scoped_refptr params_; const raw_ptr pool_; ClientSocketHandle handle_; TestCompletionCallback nested_callback_; }; TEST_F(ClientSocketPoolBaseTest, AbortAllRequestsOnFlush) { CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); // First job will be waiting until it gets aborted. connect_job_factory_->set_job_type(TestConnectJob::kMockWaitingJob); ClientSocketHandle handle; ConnectWithinCallback callback(TestGroupId("a"), params_, pool_.get()); EXPECT_EQ( ERR_IO_PENDING, handle.Init(TestGroupId("a"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); // Second job will be started during the first callback, and will // asynchronously complete with OK. connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob); pool_->FlushWithError(ERR_NETWORK_CHANGED, "Network changed"); EXPECT_THAT(callback.WaitForResult(), IsError(ERR_NETWORK_CHANGED)); EXPECT_THAT(callback.WaitForNestedResult(), IsOk()); } TEST_F(ClientSocketPoolBaseTest, BackupSocketWaitsForHostResolution) { CreatePool(kDefaultMaxSockets, kDefaultMaxSockets, true /* enable_backup_connect_jobs */); connect_job_factory_->set_job_type(TestConnectJob::kMockWaitingJob); ClientSocketHandle handle; TestCompletionCallback callback; EXPECT_EQ( ERR_IO_PENDING, handle.Init(TestGroupId("bar"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); // The backup timer fires but doesn't start a new ConnectJob while resolving // the hostname. client_socket_factory_.SetJobLoadState(0, LOAD_STATE_RESOLVING_HOST); FastForwardBy( base::Milliseconds(ClientSocketPool::kMaxConnectRetryIntervalMs * 100)); EXPECT_EQ(1, client_socket_factory_.allocation_count()); // Once the ConnectJob has finished resolving the hostname, the backup timer // will create a ConnectJob when it fires. client_socket_factory_.SetJobLoadState(0, LOAD_STATE_CONNECTING); FastForwardBy( base::Milliseconds(ClientSocketPool::kMaxConnectRetryIntervalMs)); EXPECT_EQ(2, client_socket_factory_.allocation_count()); } // Test that no backup socket is created when a ConnectJob connects before it // completes. TEST_F(ClientSocketPoolBaseTest, NoBackupSocketWhenConnected) { CreatePool(kDefaultMaxSockets, kDefaultMaxSockets, true /* enable_backup_connect_jobs */); connect_job_factory_->set_job_type(TestConnectJob::kMockWaitingJob); ClientSocketHandle handle; TestCompletionCallback callback; EXPECT_EQ( ERR_IO_PENDING, handle.Init(TestGroupId("bar"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); // The backup timer fires but doesn't start a new ConnectJob while resolving // the hostname. client_socket_factory_.SetJobLoadState(0, LOAD_STATE_RESOLVING_HOST); FastForwardBy( base::Milliseconds(ClientSocketPool::kMaxConnectRetryIntervalMs * 100)); EXPECT_EQ(1, client_socket_factory_.allocation_count()); client_socket_factory_.SetJobLoadState(0, LOAD_STATE_SSL_HANDSHAKE); client_socket_factory_.SetJobHasEstablishedConnection(0); FastForwardBy( base::Milliseconds(ClientSocketPool::kMaxConnectRetryIntervalMs * 100)); EXPECT_EQ(1, client_socket_factory_.allocation_count()); } // Cancel a pending socket request while we're at max sockets, // and verify that the backup socket firing doesn't cause a crash. TEST_F(ClientSocketPoolBaseTest, BackupSocketCancelAtMaxSockets) { // Max 4 sockets globally, max 4 sockets per group. CreatePool(kDefaultMaxSockets, kDefaultMaxSockets, true /* enable_backup_connect_jobs */); // Create the first socket and set to ERR_IO_PENDING. This starts the backup // timer. connect_job_factory_->set_job_type(TestConnectJob::kMockWaitingJob); ClientSocketHandle handle; TestCompletionCallback callback; EXPECT_EQ( ERR_IO_PENDING, handle.Init(TestGroupId("bar"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); // Start (MaxSockets - 1) connected sockets to reach max sockets. connect_job_factory_->set_job_type(TestConnectJob::kMockJob); ClientSocketHandle handles[kDefaultMaxSockets]; for (int i = 1; i < kDefaultMaxSockets; ++i) { EXPECT_EQ(OK, handles[i].Init(TestGroupId("bar"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); } base::RunLoop().RunUntilIdle(); // Cancel the pending request. handle.Reset(); // Wait for the backup timer to fire (add some slop to ensure it fires) FastForwardBy( base::Milliseconds(ClientSocketPool::kMaxConnectRetryIntervalMs / 2 * 3)); EXPECT_EQ(kDefaultMaxSockets, client_socket_factory_.allocation_count()); } TEST_F(ClientSocketPoolBaseTest, CancelBackupSocketAfterCancelingAllRequests) { CreatePool(kDefaultMaxSockets, kDefaultMaxSockets, true /* enable_backup_connect_jobs */); // Create the first socket and set to ERR_IO_PENDING. This starts the backup // timer. connect_job_factory_->set_job_type(TestConnectJob::kMockWaitingJob); ClientSocketHandle handle; TestCompletionCallback callback; EXPECT_EQ( ERR_IO_PENDING, handle.Init(TestGroupId("bar"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); ASSERT_TRUE(pool_->HasGroupForTesting(TestGroupId("bar"))); EXPECT_EQ(1u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("bar"))); EXPECT_EQ(0u, pool_->NumNeverAssignedConnectJobsInGroupForTesting( TestGroupId("bar"))); EXPECT_EQ( 0u, pool_->NumUnassignedConnectJobsInGroupForTesting(TestGroupId("bar"))); // Cancel the socket request. This should cancel the backup timer. Wait for // the backup time to see if it indeed got canceled. handle.Reset(); // Wait for the backup timer to fire (add some slop to ensure it fires) FastForwardBy( base::Milliseconds(ClientSocketPool::kMaxConnectRetryIntervalMs / 2 * 3)); ASSERT_TRUE(pool_->HasGroupForTesting(TestGroupId("bar"))); EXPECT_EQ(1u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("bar"))); } TEST_F(ClientSocketPoolBaseTest, CancelBackupSocketAfterFinishingAllRequests) { CreatePool(kDefaultMaxSockets, kDefaultMaxSockets, true /* enable_backup_connect_jobs */); // Create the first socket and set to ERR_IO_PENDING. This starts the backup // timer. connect_job_factory_->set_job_type(TestConnectJob::kMockWaitingJob); ClientSocketHandle handle; TestCompletionCallback callback; EXPECT_EQ( ERR_IO_PENDING, handle.Init(TestGroupId("bar"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob); ClientSocketHandle handle2; TestCompletionCallback callback2; EXPECT_EQ( ERR_IO_PENDING, handle2.Init(TestGroupId("bar"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback2.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); ASSERT_TRUE(pool_->HasGroupForTesting(TestGroupId("bar"))); EXPECT_EQ(2u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("bar"))); // Cancel request 1 and then complete request 2. With the requests finished, // the backup timer should be cancelled. handle.Reset(); EXPECT_THAT(callback2.WaitForResult(), IsOk()); // Wait for the backup timer to fire (add some slop to ensure it fires) FastForwardBy( base::Milliseconds(ClientSocketPool::kMaxConnectRetryIntervalMs / 2 * 3)); } // Test delayed socket binding for the case where we have two connects, // and while one is waiting on a connect, the other frees up. // The socket waiting on a connect should switch immediately to the freed // up socket. TEST_F(ClientSocketPoolBaseTest, DelayedSocketBindingWaitingForConnect) { CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob); ClientSocketHandle handle1; TestCompletionCallback callback; EXPECT_EQ( ERR_IO_PENDING, handle1.Init(TestGroupId("a"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); EXPECT_THAT(callback.WaitForResult(), IsOk()); // No idle sockets, no pending jobs. EXPECT_EQ(0, pool_->IdleSocketCount()); EXPECT_EQ(0u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); // Create a second socket to the same host, but this one will wait. connect_job_factory_->set_job_type(TestConnectJob::kMockWaitingJob); ClientSocketHandle handle2; EXPECT_EQ( ERR_IO_PENDING, handle2.Init(TestGroupId("a"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); // No idle sockets, and one connecting job. EXPECT_EQ(0, pool_->IdleSocketCount()); EXPECT_EQ(1u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); // Return the first handle to the pool. This will initiate the delayed // binding. handle1.Reset(); base::RunLoop().RunUntilIdle(); // Still no idle sockets, still one pending connect job. EXPECT_EQ(0, pool_->IdleSocketCount()); EXPECT_EQ(1u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); // The second socket connected, even though it was a Waiting Job. EXPECT_THAT(callback.WaitForResult(), IsOk()); // And we can see there is still one job waiting. EXPECT_EQ(1u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); // Finally, signal the waiting Connect. client_socket_factory_.SignalJobs(); EXPECT_EQ(0u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); base::RunLoop().RunUntilIdle(); } // Test delayed socket binding when a group is at capacity and one // of the group's sockets frees up. TEST_F(ClientSocketPoolBaseTest, DelayedSocketBindingAtGroupCapacity) { CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob); ClientSocketHandle handle1; TestCompletionCallback callback; EXPECT_EQ( ERR_IO_PENDING, handle1.Init(TestGroupId("a"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); EXPECT_THAT(callback.WaitForResult(), IsOk()); // No idle sockets, no pending jobs. EXPECT_EQ(0, pool_->IdleSocketCount()); EXPECT_EQ(0u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); // Create a second socket to the same host, but this one will wait. connect_job_factory_->set_job_type(TestConnectJob::kMockWaitingJob); ClientSocketHandle handle2; EXPECT_EQ( ERR_IO_PENDING, handle2.Init(TestGroupId("a"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); // No idle sockets, and one connecting job. EXPECT_EQ(0, pool_->IdleSocketCount()); EXPECT_EQ(1u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); // Return the first handle to the pool. This will initiate the delayed // binding. handle1.Reset(); base::RunLoop().RunUntilIdle(); // Still no idle sockets, still one pending connect job. EXPECT_EQ(0, pool_->IdleSocketCount()); EXPECT_EQ(1u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); // The second socket connected, even though it was a Waiting Job. EXPECT_THAT(callback.WaitForResult(), IsOk()); // And we can see there is still one job waiting. EXPECT_EQ(1u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); // Finally, signal the waiting Connect. client_socket_factory_.SignalJobs(); EXPECT_EQ(0u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); base::RunLoop().RunUntilIdle(); } // Test out the case where we have one socket connected, one // connecting, when the first socket finishes and goes idle. // Although the second connection is pending, the second request // should complete, by taking the first socket's idle socket. TEST_F(ClientSocketPoolBaseTest, DelayedSocketBindingAtStall) { CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob); ClientSocketHandle handle1; TestCompletionCallback callback; EXPECT_EQ( ERR_IO_PENDING, handle1.Init(TestGroupId("a"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); EXPECT_THAT(callback.WaitForResult(), IsOk()); // No idle sockets, no pending jobs. EXPECT_EQ(0, pool_->IdleSocketCount()); EXPECT_EQ(0u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); // Create a second socket to the same host, but this one will wait. connect_job_factory_->set_job_type(TestConnectJob::kMockWaitingJob); ClientSocketHandle handle2; EXPECT_EQ( ERR_IO_PENDING, handle2.Init(TestGroupId("a"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); // No idle sockets, and one connecting job. EXPECT_EQ(0, pool_->IdleSocketCount()); EXPECT_EQ(1u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); // Return the first handle to the pool. This will initiate the delayed // binding. handle1.Reset(); base::RunLoop().RunUntilIdle(); // Still no idle sockets, still one pending connect job. EXPECT_EQ(0, pool_->IdleSocketCount()); EXPECT_EQ(1u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); // The second socket connected, even though it was a Waiting Job. EXPECT_THAT(callback.WaitForResult(), IsOk()); // And we can see there is still one job waiting. EXPECT_EQ(1u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); // Finally, signal the waiting Connect. client_socket_factory_.SignalJobs(); EXPECT_EQ(0u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); base::RunLoop().RunUntilIdle(); } // Cover the case where on an available socket slot, we have one pending // request that completes synchronously, thereby making the Group empty. TEST_F(ClientSocketPoolBaseTest, SynchronouslyProcessOnePendingRequest) { const int kUnlimitedSockets = 100; const int kOneSocketPerGroup = 1; CreatePool(kUnlimitedSockets, kOneSocketPerGroup); // Make the first request asynchronous fail. // This will free up a socket slot later. connect_job_factory_->set_job_type(TestConnectJob::kMockPendingFailingJob); ClientSocketHandle handle1; TestCompletionCallback callback1; EXPECT_EQ( ERR_IO_PENDING, handle1.Init(TestGroupId("a"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback1.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); EXPECT_EQ(1u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); // Make the second request synchronously fail. This should make the Group // empty. connect_job_factory_->set_job_type(TestConnectJob::kMockFailingJob); ClientSocketHandle handle2; TestCompletionCallback callback2; // It'll be ERR_IO_PENDING now, but the TestConnectJob will synchronously fail // when created. EXPECT_EQ( ERR_IO_PENDING, handle2.Init(TestGroupId("a"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback2.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); EXPECT_EQ(1u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_THAT(callback1.WaitForResult(), IsError(ERR_CONNECTION_FAILED)); EXPECT_THAT(callback2.WaitForResult(), IsError(ERR_CONNECTION_FAILED)); EXPECT_FALSE(pool_->HasGroupForTesting(TestGroupId("a"))); } TEST_F(ClientSocketPoolBaseTest, PreferUsedSocketToUnusedSocket) { CreatePool(kDefaultMaxSockets, kDefaultMaxSockets); connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob); ClientSocketHandle handle1; TestCompletionCallback callback1; EXPECT_EQ( ERR_IO_PENDING, handle1.Init(TestGroupId("a"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback1.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); ClientSocketHandle handle2; TestCompletionCallback callback2; EXPECT_EQ( ERR_IO_PENDING, handle2.Init(TestGroupId("a"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback2.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); ClientSocketHandle handle3; TestCompletionCallback callback3; EXPECT_EQ( ERR_IO_PENDING, handle3.Init(TestGroupId("a"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback3.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); EXPECT_THAT(callback1.WaitForResult(), IsOk()); EXPECT_THAT(callback2.WaitForResult(), IsOk()); EXPECT_THAT(callback3.WaitForResult(), IsOk()); // Use the socket. EXPECT_EQ(1, handle1.socket()->Write(nullptr, 1, CompletionOnceCallback(), TRAFFIC_ANNOTATION_FOR_TESTS)); EXPECT_EQ(1, handle3.socket()->Write(nullptr, 1, CompletionOnceCallback(), TRAFFIC_ANNOTATION_FOR_TESTS)); handle1.Reset(); handle2.Reset(); handle3.Reset(); EXPECT_EQ(OK, handle1.Init( TestGroupId("a"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback1.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); EXPECT_EQ(OK, handle2.Init( TestGroupId("a"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback2.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); EXPECT_EQ(OK, handle3.Init( TestGroupId("a"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback3.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); EXPECT_TRUE(handle1.socket()->WasEverUsed()); EXPECT_TRUE(handle2.socket()->WasEverUsed()); EXPECT_FALSE(handle3.socket()->WasEverUsed()); } TEST_F(ClientSocketPoolBaseTest, RequestSockets) { CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob); TestCompletionCallback preconnect_callback; EXPECT_EQ(ERR_IO_PENDING, pool_->RequestSockets(TestGroupId("a"), params_, absl::nullopt, 2, preconnect_callback.callback(), NetLogWithSource())); ASSERT_TRUE(pool_->HasGroupForTesting(TestGroupId("a"))); EXPECT_EQ(2u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(2u, pool_->NumNeverAssignedConnectJobsInGroupForTesting( TestGroupId("a"))); EXPECT_EQ(2u, pool_->NumUnassignedConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->IdleSocketCountInGroup(TestGroupId("a"))); ClientSocketHandle handle1; TestCompletionCallback callback1; EXPECT_EQ( ERR_IO_PENDING, handle1.Init(TestGroupId("a"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback1.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); ClientSocketHandle handle2; TestCompletionCallback callback2; EXPECT_EQ( ERR_IO_PENDING, handle2.Init(TestGroupId("a"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback2.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); EXPECT_EQ(2u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumNeverAssignedConnectJobsInGroupForTesting( TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumUnassignedConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->IdleSocketCountInGroup(TestGroupId("a"))); EXPECT_THAT(preconnect_callback.WaitForResult(), IsOk()); EXPECT_THAT(callback1.WaitForResult(), IsOk()); EXPECT_THAT(callback2.WaitForResult(), IsOk()); handle1.Reset(); handle2.Reset(); EXPECT_EQ(0u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumNeverAssignedConnectJobsInGroupForTesting( TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumUnassignedConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(2u, pool_->IdleSocketCountInGroup(TestGroupId("a"))); } TEST_F(ClientSocketPoolBaseTest, RequestSocketsWhenAlreadyHaveAConnectJob) { CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob); ClientSocketHandle handle1; TestCompletionCallback callback1; EXPECT_EQ( ERR_IO_PENDING, handle1.Init(TestGroupId("a"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback1.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); ASSERT_TRUE(pool_->HasGroupForTesting(TestGroupId("a"))); EXPECT_EQ(1u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumNeverAssignedConnectJobsInGroupForTesting( TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumUnassignedConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->IdleSocketCountInGroup(TestGroupId("a"))); TestCompletionCallback preconnect_callback; EXPECT_EQ(ERR_IO_PENDING, pool_->RequestSockets(TestGroupId("a"), params_, absl::nullopt, 2, preconnect_callback.callback(), NetLogWithSource())); EXPECT_EQ(2u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(1u, pool_->NumNeverAssignedConnectJobsInGroupForTesting( TestGroupId("a"))); EXPECT_EQ(1u, pool_->NumUnassignedConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->IdleSocketCountInGroup(TestGroupId("a"))); ClientSocketHandle handle2; TestCompletionCallback callback2; EXPECT_EQ( ERR_IO_PENDING, handle2.Init(TestGroupId("a"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback2.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); EXPECT_EQ(2u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumNeverAssignedConnectJobsInGroupForTesting( TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumUnassignedConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->IdleSocketCountInGroup(TestGroupId("a"))); EXPECT_THAT(preconnect_callback.WaitForResult(), IsOk()); EXPECT_THAT(callback1.WaitForResult(), IsOk()); EXPECT_THAT(callback2.WaitForResult(), IsOk()); handle1.Reset(); handle2.Reset(); EXPECT_EQ(0u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumNeverAssignedConnectJobsInGroupForTesting( TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumUnassignedConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(2u, pool_->IdleSocketCountInGroup(TestGroupId("a"))); } TEST_F(ClientSocketPoolBaseTest, RequestSocketsWhenAlreadyHaveMultipleConnectJob) { CreatePool(4, 4); connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob); ClientSocketHandle handle1; TestCompletionCallback callback1; EXPECT_EQ( ERR_IO_PENDING, handle1.Init(TestGroupId("a"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback1.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); ClientSocketHandle handle2; TestCompletionCallback callback2; EXPECT_EQ( ERR_IO_PENDING, handle2.Init(TestGroupId("a"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback2.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); ClientSocketHandle handle3; TestCompletionCallback callback3; EXPECT_EQ( ERR_IO_PENDING, handle3.Init(TestGroupId("a"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback3.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); ASSERT_TRUE(pool_->HasGroupForTesting(TestGroupId("a"))); EXPECT_EQ(3u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumNeverAssignedConnectJobsInGroupForTesting( TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumUnassignedConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->IdleSocketCountInGroup(TestGroupId("a"))); EXPECT_EQ( OK, pool_->RequestSockets(TestGroupId("a"), params_, absl::nullopt, 2, CompletionOnceCallback(), NetLogWithSource())); EXPECT_EQ(3u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumNeverAssignedConnectJobsInGroupForTesting( TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumUnassignedConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->IdleSocketCountInGroup(TestGroupId("a"))); EXPECT_THAT(callback1.WaitForResult(), IsOk()); EXPECT_THAT(callback2.WaitForResult(), IsOk()); EXPECT_THAT(callback3.WaitForResult(), IsOk()); handle1.Reset(); handle2.Reset(); handle3.Reset(); EXPECT_EQ(0u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumNeverAssignedConnectJobsInGroupForTesting( TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumUnassignedConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(3u, pool_->IdleSocketCountInGroup(TestGroupId("a"))); } TEST_F(ClientSocketPoolBaseTest, RequestSocketsAtMaxSocketLimit) { CreatePool(kDefaultMaxSockets, kDefaultMaxSockets); connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob); ASSERT_FALSE(pool_->HasGroupForTesting(TestGroupId("a"))); TestCompletionCallback preconnect_callback; EXPECT_EQ(ERR_IO_PENDING, pool_->RequestSockets( TestGroupId("a"), params_, absl::nullopt, kDefaultMaxSockets, preconnect_callback.callback(), NetLogWithSource())); ASSERT_TRUE(pool_->HasGroupForTesting(TestGroupId("a"))); EXPECT_EQ(kDefaultMaxSockets, static_cast( pool_->NumConnectJobsInGroupForTesting(TestGroupId("a")))); EXPECT_EQ( kDefaultMaxSockets, static_cast(pool_->NumNeverAssignedConnectJobsInGroupForTesting( TestGroupId("a")))); EXPECT_EQ(kDefaultMaxSockets, static_cast(pool_->NumUnassignedConnectJobsInGroupForTesting( TestGroupId("a")))); ASSERT_FALSE(pool_->HasGroupForTesting(TestGroupId("b"))); EXPECT_EQ(OK, pool_->RequestSockets(TestGroupId("b"), params_, absl::nullopt, kDefaultMaxSockets, CompletionOnceCallback(), NetLogWithSource())); ASSERT_FALSE(pool_->HasGroupForTesting(TestGroupId("b"))); EXPECT_THAT(preconnect_callback.WaitForResult(), IsOk()); } TEST_F(ClientSocketPoolBaseTest, RequestSocketsHitMaxSocketLimit) { CreatePool(kDefaultMaxSockets, kDefaultMaxSockets); connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob); ASSERT_FALSE(pool_->HasGroupForTesting(TestGroupId("a"))); TestCompletionCallback preconnect_callback1; EXPECT_EQ(ERR_IO_PENDING, pool_->RequestSockets(TestGroupId("a"), params_, absl::nullopt, kDefaultMaxSockets - 1, preconnect_callback1.callback(), NetLogWithSource())); ASSERT_TRUE(pool_->HasGroupForTesting(TestGroupId("a"))); EXPECT_EQ(kDefaultMaxSockets - 1, static_cast( pool_->NumConnectJobsInGroupForTesting(TestGroupId("a")))); EXPECT_EQ( kDefaultMaxSockets - 1, static_cast(pool_->NumNeverAssignedConnectJobsInGroupForTesting( TestGroupId("a")))); EXPECT_EQ(kDefaultMaxSockets - 1, static_cast(pool_->NumUnassignedConnectJobsInGroupForTesting( TestGroupId("a")))); EXPECT_FALSE(pool_->IsStalled()); ASSERT_FALSE(pool_->HasGroupForTesting(TestGroupId("b"))); TestCompletionCallback preconnect_callback2; EXPECT_EQ(ERR_IO_PENDING, pool_->RequestSockets( TestGroupId("b"), params_, absl::nullopt, kDefaultMaxSockets, preconnect_callback2.callback(), NetLogWithSource())); ASSERT_TRUE(pool_->HasGroupForTesting(TestGroupId("b"))); EXPECT_EQ(1u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("b"))); EXPECT_FALSE(pool_->IsStalled()); EXPECT_THAT(preconnect_callback1.WaitForResult(), IsOk()); EXPECT_THAT(preconnect_callback2.WaitForResult(), IsOk()); } TEST_F(ClientSocketPoolBaseTest, RequestSocketsCountIdleSockets) { CreatePool(4, 4); connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob); ClientSocketHandle handle1; TestCompletionCallback callback1; EXPECT_EQ( ERR_IO_PENDING, handle1.Init(TestGroupId("a"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback1.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); ASSERT_THAT(callback1.WaitForResult(), IsOk()); handle1.Reset(); ASSERT_TRUE(pool_->HasGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumNeverAssignedConnectJobsInGroupForTesting( TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumUnassignedConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(1u, pool_->IdleSocketCountInGroup(TestGroupId("a"))); TestCompletionCallback preconnect_callback; EXPECT_EQ(ERR_IO_PENDING, pool_->RequestSockets(TestGroupId("a"), params_, absl::nullopt, 2, preconnect_callback.callback(), NetLogWithSource())); EXPECT_EQ(1u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(1u, pool_->NumNeverAssignedConnectJobsInGroupForTesting( TestGroupId("a"))); EXPECT_EQ(1u, pool_->NumUnassignedConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(1u, pool_->IdleSocketCountInGroup(TestGroupId("a"))); EXPECT_THAT(preconnect_callback.WaitForResult(), IsOk()); } TEST_F(ClientSocketPoolBaseTest, RequestSocketsCountActiveSockets) { CreatePool(4, 4); connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob); ClientSocketHandle handle1; TestCompletionCallback callback1; EXPECT_EQ( ERR_IO_PENDING, handle1.Init(TestGroupId("a"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback1.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); ASSERT_THAT(callback1.WaitForResult(), IsOk()); ASSERT_TRUE(pool_->HasGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumNeverAssignedConnectJobsInGroupForTesting( TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumUnassignedConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->IdleSocketCountInGroup(TestGroupId("a"))); EXPECT_EQ(1, pool_->NumActiveSocketsInGroupForTesting(TestGroupId("a"))); TestCompletionCallback preconnect_callback; EXPECT_EQ(ERR_IO_PENDING, pool_->RequestSockets(TestGroupId("a"), params_, absl::nullopt, 2, preconnect_callback.callback(), NetLogWithSource())); EXPECT_EQ(1u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(1u, pool_->NumNeverAssignedConnectJobsInGroupForTesting( TestGroupId("a"))); EXPECT_EQ(1u, pool_->NumUnassignedConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->IdleSocketCountInGroup(TestGroupId("a"))); EXPECT_EQ(1, pool_->NumActiveSocketsInGroupForTesting(TestGroupId("a"))); EXPECT_THAT(preconnect_callback.WaitForResult(), IsOk()); } TEST_F(ClientSocketPoolBaseTest, RequestSocketsSynchronous) { CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); connect_job_factory_->set_job_type(TestConnectJob::kMockJob); EXPECT_EQ( OK, pool_->RequestSockets(TestGroupId("a"), params_, absl::nullopt, kDefaultMaxSocketsPerGroup, CompletionOnceCallback(), NetLogWithSource())); ASSERT_TRUE(pool_->HasGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumNeverAssignedConnectJobsInGroupForTesting( TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumUnassignedConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(kDefaultMaxSocketsPerGroup, static_cast(pool_->IdleSocketCountInGroup(TestGroupId("a")))); EXPECT_EQ( OK, pool_->RequestSockets(TestGroupId("b"), params_, absl::nullopt, kDefaultMaxSocketsPerGroup, CompletionOnceCallback(), NetLogWithSource())); EXPECT_EQ(0u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("b"))); EXPECT_EQ(0u, pool_->NumNeverAssignedConnectJobsInGroupForTesting( TestGroupId("b"))); EXPECT_EQ(0u, pool_->NumUnassignedConnectJobsInGroupForTesting(TestGroupId("b"))); EXPECT_EQ(kDefaultMaxSocketsPerGroup, static_cast(pool_->IdleSocketCountInGroup(TestGroupId("b")))); } TEST_F(ClientSocketPoolBaseTest, RequestSocketsSynchronousError) { CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); connect_job_factory_->set_job_type(TestConnectJob::kMockFailingJob); EXPECT_EQ( OK, pool_->RequestSockets(TestGroupId("a"), params_, absl::nullopt, kDefaultMaxSocketsPerGroup, CompletionOnceCallback(), NetLogWithSource())); ASSERT_FALSE(pool_->HasGroupForTesting(TestGroupId("a"))); connect_job_factory_->set_job_type( TestConnectJob::kMockAdditionalErrorStateJob); EXPECT_EQ( OK, pool_->RequestSockets(TestGroupId("a"), params_, absl::nullopt, kDefaultMaxSocketsPerGroup, CompletionOnceCallback(), NetLogWithSource())); ASSERT_FALSE(pool_->HasGroupForTesting(TestGroupId("a"))); } TEST_F(ClientSocketPoolBaseTest, RequestSocketsMultipleTimesDoesNothing) { CreatePool(4, 4); connect_job_factory_->set_job_type(TestConnectJob::kMockWaitingJob); TestCompletionCallback preconnect_callback; EXPECT_EQ(ERR_IO_PENDING, pool_->RequestSockets(TestGroupId("a"), params_, absl::nullopt, 2, preconnect_callback.callback(), NetLogWithSource())); ASSERT_TRUE(pool_->HasGroupForTesting(TestGroupId("a"))); EXPECT_EQ(2u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(2u, pool_->NumNeverAssignedConnectJobsInGroupForTesting( TestGroupId("a"))); EXPECT_EQ(2u, pool_->NumUnassignedConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0, pool_->NumActiveSocketsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->IdleSocketCountInGroup(TestGroupId("a"))); EXPECT_EQ( OK, pool_->RequestSockets(TestGroupId("a"), params_, absl::nullopt, 2, CompletionOnceCallback(), NetLogWithSource())); EXPECT_EQ(2u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(2u, pool_->NumNeverAssignedConnectJobsInGroupForTesting( TestGroupId("a"))); EXPECT_EQ(2u, pool_->NumUnassignedConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0, pool_->NumActiveSocketsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->IdleSocketCountInGroup(TestGroupId("a"))); ClientSocketHandle handle1; TestCompletionCallback callback1; EXPECT_EQ( ERR_IO_PENDING, handle1.Init(TestGroupId("a"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback1.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); client_socket_factory_.SignalJob(0); EXPECT_THAT(callback1.WaitForResult(), IsOk()); EXPECT_EQ(1u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(1u, pool_->NumNeverAssignedConnectJobsInGroupForTesting( TestGroupId("a"))); EXPECT_EQ(1u, pool_->NumUnassignedConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(1, pool_->NumActiveSocketsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->IdleSocketCountInGroup(TestGroupId("a"))); ClientSocketHandle handle2; TestCompletionCallback callback2; EXPECT_EQ( ERR_IO_PENDING, handle2.Init(TestGroupId("a"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback2.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); client_socket_factory_.SignalJob(0); EXPECT_THAT(callback2.WaitForResult(), IsOk()); EXPECT_THAT(preconnect_callback.WaitForResult(), IsOk()); EXPECT_EQ(0u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumNeverAssignedConnectJobsInGroupForTesting( TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumUnassignedConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(2, pool_->NumActiveSocketsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->IdleSocketCountInGroup(TestGroupId("a"))); handle1.Reset(); handle2.Reset(); EXPECT_EQ(0u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumNeverAssignedConnectJobsInGroupForTesting( TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumUnassignedConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0, pool_->NumActiveSocketsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(2u, pool_->IdleSocketCountInGroup(TestGroupId("a"))); EXPECT_EQ( OK, pool_->RequestSockets(TestGroupId("a"), params_, absl::nullopt, 2, CompletionOnceCallback(), NetLogWithSource())); EXPECT_EQ(0u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumNeverAssignedConnectJobsInGroupForTesting( TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumUnassignedConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0, pool_->NumActiveSocketsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(2u, pool_->IdleSocketCountInGroup(TestGroupId("a"))); } TEST_F(ClientSocketPoolBaseTest, RequestSocketsDifferentNumSockets) { CreatePool(4, 4); connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob); TestCompletionCallback preconnect_callback1; EXPECT_EQ(ERR_IO_PENDING, pool_->RequestSockets(TestGroupId("a"), params_, absl::nullopt, 1, preconnect_callback1.callback(), NetLogWithSource())); ASSERT_TRUE(pool_->HasGroupForTesting(TestGroupId("a"))); EXPECT_EQ(1u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(1u, pool_->NumNeverAssignedConnectJobsInGroupForTesting( TestGroupId("a"))); EXPECT_EQ(1u, pool_->NumUnassignedConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->IdleSocketCountInGroup(TestGroupId("a"))); TestCompletionCallback preconnect_callback2; EXPECT_EQ(ERR_IO_PENDING, pool_->RequestSockets(TestGroupId("a"), params_, absl::nullopt, 2, preconnect_callback2.callback(), NetLogWithSource())); EXPECT_EQ(2u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(2u, pool_->NumNeverAssignedConnectJobsInGroupForTesting( TestGroupId("a"))); EXPECT_EQ(2u, pool_->NumUnassignedConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->IdleSocketCountInGroup(TestGroupId("a"))); TestCompletionCallback preconnect_callback3; EXPECT_EQ(ERR_IO_PENDING, pool_->RequestSockets(TestGroupId("a"), params_, absl::nullopt, 3, preconnect_callback3.callback(), NetLogWithSource())); EXPECT_EQ(3u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(3u, pool_->NumNeverAssignedConnectJobsInGroupForTesting( TestGroupId("a"))); EXPECT_EQ(3u, pool_->NumUnassignedConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->IdleSocketCountInGroup(TestGroupId("a"))); EXPECT_EQ( OK, pool_->RequestSockets(TestGroupId("a"), params_, absl::nullopt, 1, CompletionOnceCallback(), NetLogWithSource())); EXPECT_EQ(3u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(3u, pool_->NumNeverAssignedConnectJobsInGroupForTesting( TestGroupId("a"))); EXPECT_EQ(3u, pool_->NumUnassignedConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->IdleSocketCountInGroup(TestGroupId("a"))); } TEST_F(ClientSocketPoolBaseTest, PreconnectJobsTakenByNormalRequests) { CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); connect_job_factory_->set_job_type(TestConnectJob::kMockWaitingJob); TestCompletionCallback preconnect_callback; EXPECT_EQ(ERR_IO_PENDING, pool_->RequestSockets(TestGroupId("a"), params_, absl::nullopt, 1, preconnect_callback.callback(), NetLogWithSource())); ASSERT_TRUE(pool_->HasGroupForTesting(TestGroupId("a"))); EXPECT_EQ(1u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(1u, pool_->NumNeverAssignedConnectJobsInGroupForTesting( TestGroupId("a"))); EXPECT_EQ(1u, pool_->NumUnassignedConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->IdleSocketCountInGroup(TestGroupId("a"))); ClientSocketHandle handle1; TestCompletionCallback callback1; EXPECT_EQ( ERR_IO_PENDING, handle1.Init(TestGroupId("a"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback1.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); EXPECT_EQ(1u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumNeverAssignedConnectJobsInGroupForTesting( TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumUnassignedConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->IdleSocketCountInGroup(TestGroupId("a"))); client_socket_factory_.SignalJobs(); EXPECT_THAT(callback1.WaitForResult(), IsOk()); EXPECT_THAT(preconnect_callback.WaitForResult(), IsOk()); EXPECT_EQ(0u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumNeverAssignedConnectJobsInGroupForTesting( TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumUnassignedConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->IdleSocketCountInGroup(TestGroupId("a"))); EXPECT_EQ(1, pool_->NumActiveSocketsInGroupForTesting(TestGroupId("a"))); // Make sure if a preconnected socket is not fully connected when a request // starts, it has a connect start time. TestLoadTimingInfoConnectedNotReused(handle1); handle1.Reset(); EXPECT_EQ(1u, pool_->IdleSocketCountInGroup(TestGroupId("a"))); } // Checks that fully connected preconnect jobs have no connect times, and are // marked as reused. TEST_F(ClientSocketPoolBaseTest, ConnectedPreconnectJobsHaveNoConnectTimes) { CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); connect_job_factory_->set_job_type(TestConnectJob::kMockJob); EXPECT_EQ( OK, pool_->RequestSockets(TestGroupId("a"), params_, absl::nullopt, 1, CompletionOnceCallback(), NetLogWithSource())); ASSERT_TRUE(pool_->HasGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumNeverAssignedConnectJobsInGroupForTesting( TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumUnassignedConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(1u, pool_->IdleSocketCountInGroup(TestGroupId("a"))); ClientSocketHandle handle; TestCompletionCallback callback; EXPECT_EQ(OK, handle.Init( TestGroupId("a"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); // Make sure the idle socket was used. EXPECT_EQ(0u, pool_->IdleSocketCountInGroup(TestGroupId("a"))); TestLoadTimingInfoConnectedReused(handle); handle.Reset(); TestLoadTimingInfoNotConnected(handle); } // http://crbug.com/64940 regression test. TEST_F(ClientSocketPoolBaseTest, PreconnectClosesIdleSocketRemovesGroup) { const int kMaxTotalSockets = 3; const int kMaxSocketsPerGroup = 2; CreatePool(kMaxTotalSockets, kMaxSocketsPerGroup); connect_job_factory_->set_job_type(TestConnectJob::kMockWaitingJob); // Note that group id ordering matters here. "a" comes before "b", so // CloseOneIdleSocket() will try to close "a"'s idle socket. // Set up one idle socket in "a". ClientSocketHandle handle1; TestCompletionCallback callback1; EXPECT_EQ( ERR_IO_PENDING, handle1.Init(TestGroupId("a"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback1.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); ASSERT_TRUE(pool_->HasGroupForTesting(TestGroupId("a"))); EXPECT_EQ(1u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumNeverAssignedConnectJobsInGroupForTesting( TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumUnassignedConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->IdleSocketCountInGroup(TestGroupId("a"))); client_socket_factory_.SignalJobs(); ASSERT_THAT(callback1.WaitForResult(), IsOk()); EXPECT_EQ(0u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumNeverAssignedConnectJobsInGroupForTesting( TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumUnassignedConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(1, pool_->NumActiveSocketsInGroupForTesting(TestGroupId("a"))); handle1.Reset(); EXPECT_EQ(1u, pool_->IdleSocketCountInGroup(TestGroupId("a"))); // Set up two active sockets in "b". ClientSocketHandle handle2; TestCompletionCallback callback2; EXPECT_EQ( ERR_IO_PENDING, handle1.Init(TestGroupId("b"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback1.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); EXPECT_EQ( ERR_IO_PENDING, handle2.Init(TestGroupId("b"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback2.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); ASSERT_TRUE(pool_->HasGroupForTesting(TestGroupId("b"))); EXPECT_EQ(2u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("b"))); EXPECT_EQ(0u, pool_->NumNeverAssignedConnectJobsInGroupForTesting( TestGroupId("b"))); EXPECT_EQ(0u, pool_->NumUnassignedConnectJobsInGroupForTesting(TestGroupId("b"))); EXPECT_EQ(0u, pool_->IdleSocketCountInGroup(TestGroupId("b"))); client_socket_factory_.SignalJobs(); ASSERT_THAT(callback1.WaitForResult(), IsOk()); ASSERT_THAT(callback2.WaitForResult(), IsOk()); EXPECT_EQ(0u, pool_->IdleSocketCountInGroup(TestGroupId("b"))); EXPECT_EQ(0u, pool_->NumNeverAssignedConnectJobsInGroupForTesting( TestGroupId("b"))); EXPECT_EQ(0u, pool_->NumUnassignedConnectJobsInGroupForTesting(TestGroupId("b"))); EXPECT_EQ(2, pool_->NumActiveSocketsInGroupForTesting(TestGroupId("b"))); // Now we have 1 idle socket in "a" and 2 active sockets in "b". This means // we've maxed out on sockets, since we set |kMaxTotalSockets| to 3. // Requesting 2 preconnected sockets for "a" should fail to allocate any more // sockets for "a", and "b" should still have 2 active sockets. EXPECT_EQ( OK, pool_->RequestSockets(TestGroupId("a"), params_, absl::nullopt, 2, CompletionOnceCallback(), NetLogWithSource())); EXPECT_EQ(0u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumNeverAssignedConnectJobsInGroupForTesting( TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumUnassignedConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(1u, pool_->IdleSocketCountInGroup(TestGroupId("a"))); EXPECT_EQ(0, pool_->NumActiveSocketsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("b"))); EXPECT_EQ(0u, pool_->NumNeverAssignedConnectJobsInGroupForTesting( TestGroupId("b"))); EXPECT_EQ(0u, pool_->NumUnassignedConnectJobsInGroupForTesting(TestGroupId("b"))); EXPECT_EQ(0u, pool_->IdleSocketCountInGroup(TestGroupId("b"))); EXPECT_EQ(2, pool_->NumActiveSocketsInGroupForTesting(TestGroupId("b"))); // Now release the 2 active sockets for "b". This will give us 1 idle socket // in "a" and 2 idle sockets in "b". Requesting 2 preconnected sockets for // "a" should result in closing 1 for "b". handle1.Reset(); handle2.Reset(); EXPECT_EQ(2u, pool_->IdleSocketCountInGroup(TestGroupId("b"))); EXPECT_EQ(0, pool_->NumActiveSocketsInGroupForTesting(TestGroupId("b"))); TestCompletionCallback preconnect_callback; EXPECT_EQ(ERR_IO_PENDING, pool_->RequestSockets(TestGroupId("a"), params_, absl::nullopt, 2, preconnect_callback.callback(), NetLogWithSource())); EXPECT_EQ(1u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(1u, pool_->NumNeverAssignedConnectJobsInGroupForTesting( TestGroupId("a"))); EXPECT_EQ(1u, pool_->NumUnassignedConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(1u, pool_->IdleSocketCountInGroup(TestGroupId("a"))); EXPECT_EQ(0, pool_->NumActiveSocketsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("b"))); EXPECT_EQ(0u, pool_->NumNeverAssignedConnectJobsInGroupForTesting( TestGroupId("b"))); EXPECT_EQ(0u, pool_->NumUnassignedConnectJobsInGroupForTesting(TestGroupId("b"))); EXPECT_EQ(1u, pool_->IdleSocketCountInGroup(TestGroupId("b"))); EXPECT_EQ(0, pool_->NumActiveSocketsInGroupForTesting(TestGroupId("b"))); } TEST_F(ClientSocketPoolBaseTest, PreconnectWithoutBackupJob) { CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup, true /* enable_backup_connect_jobs */); // Make the ConnectJob hang until it times out, shorten the timeout. connect_job_factory_->set_job_type(TestConnectJob::kMockWaitingJob); connect_job_factory_->set_timeout_duration(base::Milliseconds(500)); TestCompletionCallback preconnect_callback; EXPECT_EQ(ERR_IO_PENDING, pool_->RequestSockets(TestGroupId("a"), params_, absl::nullopt, 1, preconnect_callback.callback(), NetLogWithSource())); EXPECT_EQ(1u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(1u, pool_->NumNeverAssignedConnectJobsInGroupForTesting( TestGroupId("a"))); EXPECT_EQ(1u, pool_->NumUnassignedConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->IdleSocketCountInGroup(TestGroupId("a"))); // Verify the backup timer doesn't create a backup job, by making // the backup job a pending job instead of a waiting job, so it // *would* complete if it were created. base::RunLoop loop; connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob); base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask( FROM_HERE, loop.QuitClosure(), base::Seconds(1)); loop.Run(); EXPECT_FALSE(pool_->HasGroupForTesting(TestGroupId("a"))); } TEST_F(ClientSocketPoolBaseTest, PreconnectWithBackupJob) { CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup, true /* enable_backup_connect_jobs */); // Make the ConnectJob hang forever. connect_job_factory_->set_job_type(TestConnectJob::kMockWaitingJob); TestCompletionCallback preconnect_callback; EXPECT_EQ(ERR_IO_PENDING, pool_->RequestSockets(TestGroupId("a"), params_, absl::nullopt, 1, preconnect_callback.callback(), NetLogWithSource())); EXPECT_EQ(1u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(1u, pool_->NumNeverAssignedConnectJobsInGroupForTesting( TestGroupId("a"))); EXPECT_EQ(1u, pool_->NumUnassignedConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->IdleSocketCountInGroup(TestGroupId("a"))); base::RunLoop().RunUntilIdle(); // Make the backup job be a pending job, so it completes normally. connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob); ClientSocketHandle handle; TestCompletionCallback callback; EXPECT_EQ( ERR_IO_PENDING, handle.Init(TestGroupId("a"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); // Timer has started, but the backup connect job shouldn't be created yet. EXPECT_EQ(1u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumNeverAssignedConnectJobsInGroupForTesting( TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumUnassignedConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->IdleSocketCountInGroup(TestGroupId("a"))); EXPECT_EQ(0, pool_->NumActiveSocketsInGroupForTesting(TestGroupId("a"))); ASSERT_THAT(callback.WaitForResult(), IsOk()); // The hung connect job should still be there, but everything else should be // complete. EXPECT_EQ(1u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumNeverAssignedConnectJobsInGroupForTesting( TestGroupId("a"))); EXPECT_EQ(1u, pool_->NumUnassignedConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->IdleSocketCountInGroup(TestGroupId("a"))); EXPECT_EQ(1, pool_->NumActiveSocketsInGroupForTesting(TestGroupId("a"))); } // Tests that a preconnect that starts out with unread data can still be used. // http://crbug.com/334467 TEST_F(ClientSocketPoolBaseTest, PreconnectWithUnreadData) { CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); connect_job_factory_->set_job_type(TestConnectJob::kMockUnreadDataJob); EXPECT_EQ( OK, pool_->RequestSockets(TestGroupId("a"), params_, absl::nullopt, 1, CompletionOnceCallback(), NetLogWithSource())); ASSERT_TRUE(pool_->HasGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumNeverAssignedConnectJobsInGroupForTesting( TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumUnassignedConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(1u, pool_->IdleSocketCountInGroup(TestGroupId("a"))); // Fail future jobs to be sure that handle receives the preconnected socket // rather than closing it and making a new one. connect_job_factory_->set_job_type(TestConnectJob::kMockFailingJob); ClientSocketHandle handle; TestCompletionCallback callback; EXPECT_EQ(OK, handle.Init( TestGroupId("a"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); ASSERT_TRUE(pool_->HasGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumNeverAssignedConnectJobsInGroupForTesting( TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumUnassignedConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->IdleSocketCountInGroup(TestGroupId("a"))); EXPECT_EQ(1, pool_->NumActiveSocketsInGroupForTesting(TestGroupId("a"))); // Drain the pending read. EXPECT_EQ(1, handle.socket()->Read(nullptr, 1, CompletionOnceCallback())); TestLoadTimingInfoConnectedReused(handle); handle.Reset(); // The socket should be usable now that it's idle again. EXPECT_EQ(1u, pool_->IdleSocketCountInGroup(TestGroupId("a"))); } TEST_F(ClientSocketPoolBaseTest, RequestGetsAssignedJob) { CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); connect_job_factory_->set_job_type(TestConnectJob::kMockWaitingJob); ClientSocketHandle handle1; TestCompletionCallback callback1; EXPECT_EQ( ERR_IO_PENDING, handle1.Init(TestGroupId("a"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback1.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); EXPECT_EQ(1u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumNeverAssignedConnectJobsInGroupForTesting( TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumUnassignedConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->IdleSocketCountInGroup(TestGroupId("a"))); EXPECT_TRUE(pool_->RequestInGroupWithHandleHasJobForTesting(TestGroupId("a"), &handle1)); } TEST_F(ClientSocketPoolBaseTest, MultipleRequestsGetAssignedJobs) { CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); connect_job_factory_->set_job_type(TestConnectJob::kMockWaitingJob); ClientSocketHandle handle1; TestCompletionCallback callback1; EXPECT_EQ( ERR_IO_PENDING, handle1.Init(TestGroupId("a"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback1.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); EXPECT_EQ(1u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumNeverAssignedConnectJobsInGroupForTesting( TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumUnassignedConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->IdleSocketCountInGroup(TestGroupId("a"))); ClientSocketHandle handle2; TestCompletionCallback callback2; EXPECT_EQ( ERR_IO_PENDING, handle2.Init(TestGroupId("a"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback2.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); EXPECT_EQ(2u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumNeverAssignedConnectJobsInGroupForTesting( TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumUnassignedConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->IdleSocketCountInGroup(TestGroupId("a"))); EXPECT_TRUE(pool_->RequestInGroupWithHandleHasJobForTesting(TestGroupId("a"), &handle1)); EXPECT_TRUE(pool_->RequestInGroupWithHandleHasJobForTesting(TestGroupId("a"), &handle2)); // One job completes. The other request should still have its job. client_socket_factory_.SignalJob(0); EXPECT_THAT(callback1.WaitForResult(), IsOk()); EXPECT_EQ(1u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumNeverAssignedConnectJobsInGroupForTesting( TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumUnassignedConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(1, pool_->NumActiveSocketsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->IdleSocketCountInGroup(TestGroupId("a"))); EXPECT_TRUE(pool_->RequestInGroupWithHandleHasJobForTesting(TestGroupId("a"), &handle2)); } TEST_F(ClientSocketPoolBaseTest, PreconnectJobGetsAssignedToRequest) { CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); connect_job_factory_->set_job_type(TestConnectJob::kMockWaitingJob); TestCompletionCallback preconnect_callback; EXPECT_EQ(ERR_IO_PENDING, pool_->RequestSockets(TestGroupId("a"), params_, absl::nullopt, 1, preconnect_callback.callback(), NetLogWithSource())); ASSERT_TRUE(pool_->HasGroupForTesting(TestGroupId("a"))); EXPECT_EQ(1u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(1u, pool_->NumNeverAssignedConnectJobsInGroupForTesting( TestGroupId("a"))); EXPECT_EQ(1u, pool_->NumUnassignedConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->IdleSocketCountInGroup(TestGroupId("a"))); ClientSocketHandle handle1; TestCompletionCallback callback1; EXPECT_EQ( ERR_IO_PENDING, handle1.Init(TestGroupId("a"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback1.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); EXPECT_EQ(1u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumNeverAssignedConnectJobsInGroupForTesting( TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumUnassignedConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->IdleSocketCountInGroup(TestGroupId("a"))); EXPECT_TRUE(pool_->RequestInGroupWithHandleHasJobForTesting(TestGroupId("a"), &handle1)); } TEST_F(ClientSocketPoolBaseTest, HigherPriorityRequestStealsJob) { CreatePool(kDefaultMaxSockets, 1); connect_job_factory_->set_job_type(TestConnectJob::kMockWaitingJob); ClientSocketHandle handle1; TestCompletionCallback callback1; EXPECT_EQ( ERR_IO_PENDING, handle1.Init(TestGroupId("a"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback1.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); EXPECT_EQ(1u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumNeverAssignedConnectJobsInGroupForTesting( TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumUnassignedConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->IdleSocketCountInGroup(TestGroupId("a"))); EXPECT_TRUE(pool_->RequestInGroupWithHandleHasJobForTesting(TestGroupId("a"), &handle1)); // Insert a higher priority request ClientSocketHandle handle2; TestCompletionCallback callback2; EXPECT_EQ( ERR_IO_PENDING, handle2.Init(TestGroupId("a"), params_, absl::nullopt, HIGHEST, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback2.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); EXPECT_EQ(1u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumNeverAssignedConnectJobsInGroupForTesting( TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumUnassignedConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->IdleSocketCountInGroup(TestGroupId("a"))); // The highest priority request should steal the job from the default priority // request. EXPECT_TRUE(pool_->RequestInGroupWithHandleHasJobForTesting(TestGroupId("a"), &handle2)); EXPECT_FALSE(pool_->RequestInGroupWithHandleHasJobForTesting(TestGroupId("a"), &handle1)); } TEST_F(ClientSocketPoolBaseTest, RequestStealsJobFromLowestRequestWithJob) { CreatePool(3, 3); connect_job_factory_->set_job_type(TestConnectJob::kMockWaitingJob); ClientSocketHandle handle_lowest; TestCompletionCallback callback_lowest; EXPECT_EQ( ERR_IO_PENDING, handle_lowest.Init(TestGroupId("a"), params_, absl::nullopt, LOWEST, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback_lowest.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); EXPECT_EQ(1u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumNeverAssignedConnectJobsInGroupForTesting( TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumUnassignedConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->IdleSocketCountInGroup(TestGroupId("a"))); ClientSocketHandle handle_highest; TestCompletionCallback callback_highest; EXPECT_EQ( ERR_IO_PENDING, handle_highest.Init(TestGroupId("a"), params_, absl::nullopt, HIGHEST, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback_highest.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); EXPECT_EQ(2u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumNeverAssignedConnectJobsInGroupForTesting( TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumUnassignedConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->IdleSocketCountInGroup(TestGroupId("a"))); ClientSocketHandle handle_low; TestCompletionCallback callback_low; EXPECT_EQ(ERR_IO_PENDING, handle_low.Init( TestGroupId("a"), params_, absl::nullopt, LOW, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback_low.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); EXPECT_EQ(3u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumNeverAssignedConnectJobsInGroupForTesting( TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumUnassignedConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->IdleSocketCountInGroup(TestGroupId("a"))); ClientSocketHandle handle_lowest2; TestCompletionCallback callback_lowest2; EXPECT_EQ( ERR_IO_PENDING, handle_lowest2.Init(TestGroupId("a"), params_, absl::nullopt, LOWEST, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback_lowest2.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); EXPECT_EQ(3u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumNeverAssignedConnectJobsInGroupForTesting( TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumUnassignedConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->IdleSocketCountInGroup(TestGroupId("a"))); // The top three requests in the queue should have jobs. EXPECT_TRUE(pool_->RequestInGroupWithHandleHasJobForTesting(TestGroupId("a"), &handle_highest)); EXPECT_TRUE(pool_->RequestInGroupWithHandleHasJobForTesting(TestGroupId("a"), &handle_low)); EXPECT_TRUE(pool_->RequestInGroupWithHandleHasJobForTesting(TestGroupId("a"), &handle_lowest)); EXPECT_FALSE(pool_->RequestInGroupWithHandleHasJobForTesting( TestGroupId("a"), &handle_lowest2)); // Add another request with medium priority. It should steal the job from the // lowest priority request with a job. ClientSocketHandle handle_medium; TestCompletionCallback callback_medium; EXPECT_EQ( ERR_IO_PENDING, handle_medium.Init(TestGroupId("a"), params_, absl::nullopt, MEDIUM, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback_medium.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); EXPECT_EQ(3u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumNeverAssignedConnectJobsInGroupForTesting( TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumUnassignedConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->IdleSocketCountInGroup(TestGroupId("a"))); EXPECT_TRUE(pool_->RequestInGroupWithHandleHasJobForTesting(TestGroupId("a"), &handle_highest)); EXPECT_TRUE(pool_->RequestInGroupWithHandleHasJobForTesting(TestGroupId("a"), &handle_medium)); EXPECT_TRUE(pool_->RequestInGroupWithHandleHasJobForTesting(TestGroupId("a"), &handle_low)); EXPECT_FALSE(pool_->RequestInGroupWithHandleHasJobForTesting(TestGroupId("a"), &handle_lowest)); EXPECT_FALSE(pool_->RequestInGroupWithHandleHasJobForTesting( TestGroupId("a"), &handle_lowest2)); } TEST_F(ClientSocketPoolBaseTest, ReprioritizeRequestStealsJob) { CreatePool(kDefaultMaxSockets, 1); connect_job_factory_->set_job_type(TestConnectJob::kMockWaitingJob); ClientSocketHandle handle1; TestCompletionCallback callback1; EXPECT_EQ( ERR_IO_PENDING, handle1.Init(TestGroupId("a"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback1.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); EXPECT_EQ(1u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumNeverAssignedConnectJobsInGroupForTesting( TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumUnassignedConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->IdleSocketCountInGroup(TestGroupId("a"))); ClientSocketHandle handle2; TestCompletionCallback callback2; EXPECT_EQ( ERR_IO_PENDING, handle2.Init(TestGroupId("a"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback2.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); EXPECT_EQ(1u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumNeverAssignedConnectJobsInGroupForTesting( TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumUnassignedConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->IdleSocketCountInGroup(TestGroupId("a"))); // The second request doesn't get a job because we are at the limit. EXPECT_TRUE(pool_->RequestInGroupWithHandleHasJobForTesting(TestGroupId("a"), &handle1)); EXPECT_FALSE(pool_->RequestInGroupWithHandleHasJobForTesting(TestGroupId("a"), &handle2)); // Reprioritizing the second request places it above the first, and it steals // the job from the first request. pool_->SetPriority(TestGroupId("a"), &handle2, HIGHEST); EXPECT_TRUE(pool_->RequestInGroupWithHandleHasJobForTesting(TestGroupId("a"), &handle2)); EXPECT_FALSE(pool_->RequestInGroupWithHandleHasJobForTesting(TestGroupId("a"), &handle1)); } TEST_F(ClientSocketPoolBaseTest, CancelRequestReassignsJob) { CreatePool(kDefaultMaxSockets, 1); connect_job_factory_->set_job_type(TestConnectJob::kMockWaitingJob); ClientSocketHandle handle1; TestCompletionCallback callback1; EXPECT_EQ( ERR_IO_PENDING, handle1.Init(TestGroupId("a"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback1.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); EXPECT_EQ(1u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumNeverAssignedConnectJobsInGroupForTesting( TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumUnassignedConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->IdleSocketCountInGroup(TestGroupId("a"))); EXPECT_TRUE(pool_->RequestInGroupWithHandleHasJobForTesting(TestGroupId("a"), &handle1)); ClientSocketHandle handle2; TestCompletionCallback callback2; EXPECT_EQ( ERR_IO_PENDING, handle2.Init(TestGroupId("a"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback2.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); EXPECT_EQ(1u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumNeverAssignedConnectJobsInGroupForTesting( TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumUnassignedConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->IdleSocketCountInGroup(TestGroupId("a"))); // The second request doesn't get a job because we are the limit. EXPECT_TRUE(pool_->RequestInGroupWithHandleHasJobForTesting(TestGroupId("a"), &handle1)); EXPECT_FALSE(pool_->RequestInGroupWithHandleHasJobForTesting(TestGroupId("a"), &handle2)); // The second request should get a job upon cancelling the first request. handle1.Reset(); EXPECT_EQ(1u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumNeverAssignedConnectJobsInGroupForTesting( TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumUnassignedConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->IdleSocketCountInGroup(TestGroupId("a"))); EXPECT_TRUE(pool_->RequestInGroupWithHandleHasJobForTesting(TestGroupId("a"), &handle2)); } TEST_F(ClientSocketPoolBaseTest, JobCompletionReassignsJob) { CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); connect_job_factory_->set_job_type(TestConnectJob::kMockWaitingJob); ClientSocketHandle handle1; TestCompletionCallback callback1; EXPECT_EQ( ERR_IO_PENDING, handle1.Init(TestGroupId("a"), params_, absl::nullopt, HIGHEST, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback1.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); EXPECT_EQ(1u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumNeverAssignedConnectJobsInGroupForTesting( TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumUnassignedConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->IdleSocketCountInGroup(TestGroupId("a"))); ClientSocketHandle handle2; TestCompletionCallback callback2; EXPECT_EQ( ERR_IO_PENDING, handle2.Init(TestGroupId("a"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback2.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); EXPECT_EQ(2u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumNeverAssignedConnectJobsInGroupForTesting( TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumUnassignedConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->IdleSocketCountInGroup(TestGroupId("a"))); EXPECT_TRUE(pool_->RequestInGroupWithHandleHasJobForTesting(TestGroupId("a"), &handle1)); EXPECT_TRUE(pool_->RequestInGroupWithHandleHasJobForTesting(TestGroupId("a"), &handle2)); // The lower-priority job completes first. The higher-priority request should // get the socket, and the lower-priority request should get the remaining // job. client_socket_factory_.SignalJob(1); EXPECT_THAT(callback1.WaitForResult(), IsOk()); EXPECT_EQ(1u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumNeverAssignedConnectJobsInGroupForTesting( TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumUnassignedConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(1, pool_->NumActiveSocketsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->IdleSocketCountInGroup(TestGroupId("a"))); EXPECT_TRUE(handle1.socket()); EXPECT_TRUE(pool_->RequestInGroupWithHandleHasJobForTesting(TestGroupId("a"), &handle2)); } class MockLayeredPool : public HigherLayeredPool { public: MockLayeredPool(TransportClientSocketPool* pool, const ClientSocketPool::GroupId& group_id) : pool_(pool), group_id_(group_id) { pool_->AddHigherLayeredPool(this); } ~MockLayeredPool() override { pool_->RemoveHigherLayeredPool(this); } int RequestSocket(TransportClientSocketPool* pool) { return handle_.Init( group_id_, ClientSocketPool::SocketParams::CreateForHttpForTesting(), absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback_.callback(), ClientSocketPool::ProxyAuthCallback(), pool, NetLogWithSource()); } int RequestSocketWithoutLimits(TransportClientSocketPool* pool) { return handle_.Init( group_id_, ClientSocketPool::SocketParams::CreateForHttpForTesting(), absl::nullopt, MAXIMUM_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::DISABLED, callback_.callback(), ClientSocketPool::ProxyAuthCallback(), pool, NetLogWithSource()); } bool ReleaseOneConnection() { if (!handle_.is_initialized() || !can_release_connection_) { return false; } handle_.socket()->Disconnect(); handle_.Reset(); return true; } void set_can_release_connection(bool can_release_connection) { can_release_connection_ = can_release_connection; } MOCK_METHOD0(CloseOneIdleConnection, bool()); private: const raw_ptr pool_; ClientSocketHandle handle_; TestCompletionCallback callback_; const ClientSocketPool::GroupId group_id_; bool can_release_connection_ = true; }; // Tests the basic case of closing an idle socket in a higher layered pool when // a new request is issued and the lower layer pool is stalled. TEST_F(ClientSocketPoolBaseTest, CloseIdleSocketsHeldByLayeredPoolWhenNeeded) { CreatePool(1, 1); connect_job_factory_->set_job_type(TestConnectJob::kMockJob); MockLayeredPool mock_layered_pool(pool_.get(), TestGroupId("foo")); EXPECT_THAT(mock_layered_pool.RequestSocket(pool_.get()), IsOk()); EXPECT_CALL(mock_layered_pool, CloseOneIdleConnection()) .WillOnce(Invoke(&mock_layered_pool, &MockLayeredPool::ReleaseOneConnection)); ClientSocketHandle handle; TestCompletionCallback callback; EXPECT_EQ( ERR_IO_PENDING, handle.Init(TestGroupId("a"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); EXPECT_THAT(callback.WaitForResult(), IsOk()); } // Tests the case that trying to close an idle socket in a higher layered pool // fails. TEST_F(ClientSocketPoolBaseTest, CloseIdleSocketsHeldByLayeredPoolWhenNeededFails) { CreatePool(1, 1); connect_job_factory_->set_job_type(TestConnectJob::kMockJob); MockLayeredPool mock_layered_pool(pool_.get(), TestGroupId("foo")); mock_layered_pool.set_can_release_connection(false); EXPECT_THAT(mock_layered_pool.RequestSocket(pool_.get()), IsOk()); EXPECT_CALL(mock_layered_pool, CloseOneIdleConnection()) .WillOnce(Invoke(&mock_layered_pool, &MockLayeredPool::ReleaseOneConnection)); ClientSocketHandle handle; TestCompletionCallback callback; EXPECT_EQ( ERR_IO_PENDING, handle.Init(TestGroupId("a"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); base::RunLoop().RunUntilIdle(); EXPECT_FALSE(callback.have_result()); } // Same as above, but the idle socket is in the same group as the stalled // socket, and closes the only other request in its group when closing requests // in higher layered pools. This generally shouldn't happen, but it may be // possible if a higher level pool issues a request and the request is // subsequently cancelled. Even if it's not possible, best not to crash. TEST_F(ClientSocketPoolBaseTest, CloseIdleSocketsHeldByLayeredPoolWhenNeededSameGroup) { CreatePool(2, 2); connect_job_factory_->set_job_type(TestConnectJob::kMockJob); // Need a socket in another group for the pool to be stalled (If a group // has the maximum number of connections already, it's not stalled). ClientSocketHandle handle1; TestCompletionCallback callback1; EXPECT_EQ(OK, handle1.Init(TestGroupId("group1"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback1.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); MockLayeredPool mock_layered_pool(pool_.get(), TestGroupId("group2")); EXPECT_THAT(mock_layered_pool.RequestSocket(pool_.get()), IsOk()); EXPECT_CALL(mock_layered_pool, CloseOneIdleConnection()) .WillOnce(Invoke(&mock_layered_pool, &MockLayeredPool::ReleaseOneConnection)); ClientSocketHandle handle; TestCompletionCallback callback2; EXPECT_EQ(ERR_IO_PENDING, handle.Init( TestGroupId("group2"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback2.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); EXPECT_THAT(callback2.WaitForResult(), IsOk()); } // Tests the case when an idle socket can be closed when a new request is // issued, and the new request belongs to a group that was previously stalled. TEST_F(ClientSocketPoolBaseTest, CloseIdleSocketsHeldByLayeredPoolInSameGroupWhenNeeded) { CreatePool(2, 2); std::list job_types; job_types.push_back(TestConnectJob::kMockJob); job_types.push_back(TestConnectJob::kMockJob); job_types.push_back(TestConnectJob::kMockJob); job_types.push_back(TestConnectJob::kMockJob); connect_job_factory_->set_job_types(&job_types); ClientSocketHandle handle1; TestCompletionCallback callback1; EXPECT_EQ(OK, handle1.Init(TestGroupId("group1"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback1.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); MockLayeredPool mock_layered_pool(pool_.get(), TestGroupId("group2")); EXPECT_THAT(mock_layered_pool.RequestSocket(pool_.get()), IsOk()); EXPECT_CALL(mock_layered_pool, CloseOneIdleConnection()) .WillRepeatedly(Invoke(&mock_layered_pool, &MockLayeredPool::ReleaseOneConnection)); mock_layered_pool.set_can_release_connection(false); // The third request is made when the socket pool is in a stalled state. ClientSocketHandle handle3; TestCompletionCallback callback3; EXPECT_EQ(ERR_IO_PENDING, handle3.Init( TestGroupId("group3"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback3.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); base::RunLoop().RunUntilIdle(); EXPECT_FALSE(callback3.have_result()); // The fourth request is made when the pool is no longer stalled. The third // request should be serviced first, since it was issued first and has the // same priority. mock_layered_pool.set_can_release_connection(true); ClientSocketHandle handle4; TestCompletionCallback callback4; EXPECT_EQ(ERR_IO_PENDING, handle4.Init( TestGroupId("group3"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback4.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); EXPECT_THAT(callback3.WaitForResult(), IsOk()); EXPECT_FALSE(callback4.have_result()); // Closing a handle should free up another socket slot. handle1.Reset(); EXPECT_THAT(callback4.WaitForResult(), IsOk()); } // Tests the case when an idle socket can be closed when a new request is // issued, and the new request belongs to a group that was previously stalled. // // The two differences from the above test are that the stalled requests are not // in the same group as the layered pool's request, and the the fourth request // has a higher priority than the third one, so gets a socket first. TEST_F(ClientSocketPoolBaseTest, CloseIdleSocketsHeldByLayeredPoolInSameGroupWhenNeeded2) { CreatePool(2, 2); std::list job_types; job_types.push_back(TestConnectJob::kMockJob); job_types.push_back(TestConnectJob::kMockJob); job_types.push_back(TestConnectJob::kMockJob); job_types.push_back(TestConnectJob::kMockJob); connect_job_factory_->set_job_types(&job_types); ClientSocketHandle handle1; TestCompletionCallback callback1; EXPECT_EQ(OK, handle1.Init(TestGroupId("group1"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback1.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); MockLayeredPool mock_layered_pool(pool_.get(), TestGroupId("group2")); EXPECT_THAT(mock_layered_pool.RequestSocket(pool_.get()), IsOk()); EXPECT_CALL(mock_layered_pool, CloseOneIdleConnection()) .WillRepeatedly(Invoke(&mock_layered_pool, &MockLayeredPool::ReleaseOneConnection)); mock_layered_pool.set_can_release_connection(false); // The third request is made when the socket pool is in a stalled state. ClientSocketHandle handle3; TestCompletionCallback callback3; EXPECT_EQ( ERR_IO_PENDING, handle3.Init(TestGroupId("group3"), params_, absl::nullopt, MEDIUM, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback3.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); base::RunLoop().RunUntilIdle(); EXPECT_FALSE(callback3.have_result()); // The fourth request is made when the pool is no longer stalled. This // request has a higher priority than the third request, so is serviced first. mock_layered_pool.set_can_release_connection(true); ClientSocketHandle handle4; TestCompletionCallback callback4; EXPECT_EQ( ERR_IO_PENDING, handle4.Init(TestGroupId("group3"), params_, absl::nullopt, HIGHEST, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback4.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); EXPECT_THAT(callback4.WaitForResult(), IsOk()); EXPECT_FALSE(callback3.have_result()); // Closing a handle should free up another socket slot. handle1.Reset(); EXPECT_THAT(callback3.WaitForResult(), IsOk()); } TEST_F(ClientSocketPoolBaseTest, CloseMultipleIdleSocketsHeldByLayeredPoolWhenNeeded) { CreatePool(1, 1); connect_job_factory_->set_job_type(TestConnectJob::kMockJob); MockLayeredPool mock_layered_pool1(pool_.get(), TestGroupId("foo")); EXPECT_THAT(mock_layered_pool1.RequestSocket(pool_.get()), IsOk()); EXPECT_CALL(mock_layered_pool1, CloseOneIdleConnection()) .WillRepeatedly(Invoke(&mock_layered_pool1, &MockLayeredPool::ReleaseOneConnection)); MockLayeredPool mock_layered_pool2(pool_.get(), TestGroupId("bar")); EXPECT_THAT(mock_layered_pool2.RequestSocketWithoutLimits(pool_.get()), IsOk()); EXPECT_CALL(mock_layered_pool2, CloseOneIdleConnection()) .WillRepeatedly(Invoke(&mock_layered_pool2, &MockLayeredPool::ReleaseOneConnection)); ClientSocketHandle handle; TestCompletionCallback callback; EXPECT_EQ( ERR_IO_PENDING, handle.Init(TestGroupId("a"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); EXPECT_THAT(callback.WaitForResult(), IsOk()); } // Test that when a socket pool and group are at their limits, a request // with RespectLimits::DISABLED triggers creation of a new socket, and gets the // socket instead of a request with the same priority that was issued earlier, // but has RespectLimits::ENABLED. TEST_F(ClientSocketPoolBaseTest, IgnoreLimits) { CreatePool(1, 1); // Issue a request to reach the socket pool limit. EXPECT_EQ(OK, StartRequestWithIgnoreLimits( TestGroupId("a"), MAXIMUM_PRIORITY, ClientSocketPool::RespectLimits::ENABLED)); EXPECT_EQ(0u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob); EXPECT_EQ(ERR_IO_PENDING, StartRequestWithIgnoreLimits( TestGroupId("a"), MAXIMUM_PRIORITY, ClientSocketPool::RespectLimits::ENABLED)); EXPECT_EQ(0u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); // Issue a request that ignores the limits, so a new ConnectJob is // created. EXPECT_EQ(ERR_IO_PENDING, StartRequestWithIgnoreLimits( TestGroupId("a"), MAXIMUM_PRIORITY, ClientSocketPool::RespectLimits::DISABLED)); ASSERT_EQ(1u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_THAT(request(2)->WaitForResult(), IsOk()); EXPECT_FALSE(request(1)->have_result()); } // Test that when a socket pool and group are at their limits, a ConnectJob // issued for a request with RespectLimits::DISABLED is not cancelled when a // request with RespectLimits::ENABLED issued to the same group is cancelled. TEST_F(ClientSocketPoolBaseTest, IgnoreLimitsCancelOtherJob) { CreatePool(1, 1); // Issue a request to reach the socket pool limit. EXPECT_EQ(OK, StartRequestWithIgnoreLimits( TestGroupId("a"), MAXIMUM_PRIORITY, ClientSocketPool::RespectLimits::ENABLED)); EXPECT_EQ(0u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob); EXPECT_EQ(ERR_IO_PENDING, StartRequestWithIgnoreLimits( TestGroupId("a"), MAXIMUM_PRIORITY, ClientSocketPool::RespectLimits::ENABLED)); EXPECT_EQ(0u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); // Issue a request with RespectLimits::DISABLED, so a new ConnectJob is // created. EXPECT_EQ(ERR_IO_PENDING, StartRequestWithIgnoreLimits( TestGroupId("a"), MAXIMUM_PRIORITY, ClientSocketPool::RespectLimits::DISABLED)); ASSERT_EQ(1u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); // Cancel the pending request with RespectLimits::ENABLED. The ConnectJob // should not be cancelled. request(1)->handle()->Reset(); ASSERT_EQ(1u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_THAT(request(2)->WaitForResult(), IsOk()); EXPECT_FALSE(request(1)->have_result()); } TEST_F(ClientSocketPoolBaseTest, ProxyAuthNoAuthCallback) { CreatePool(1, 1); connect_job_factory_->set_job_type(TestConnectJob::kMockAuthChallengeOnceJob); ClientSocketHandle handle; TestCompletionCallback callback; EXPECT_EQ( ERR_IO_PENDING, handle.Init(TestGroupId("a"), params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource())); EXPECT_EQ(1u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_THAT(callback.WaitForResult(), IsError(ERR_PROXY_AUTH_REQUESTED)); EXPECT_FALSE(handle.is_initialized()); EXPECT_FALSE(handle.socket()); // The group should now be empty, and thus be deleted. EXPECT_FALSE(pool_->HasGroupForTesting(TestGroupId("a"))); } class TestAuthHelper { public: TestAuthHelper() = default; TestAuthHelper(const TestAuthHelper&) = delete; TestAuthHelper& operator=(const TestAuthHelper&) = delete; ~TestAuthHelper() = default; void InitHandle( scoped_refptr params, TransportClientSocketPool* pool, RequestPriority priority = DEFAULT_PRIORITY, ClientSocketPool::RespectLimits respect_limits = ClientSocketPool::RespectLimits::ENABLED, const ClientSocketPool::GroupId& group_id_in = TestGroupId("a")) { EXPECT_EQ(ERR_IO_PENDING, handle_.Init(group_id_in, params, absl::nullopt, priority, SocketTag(), respect_limits, callback_.callback(), base::BindRepeating(&TestAuthHelper::AuthCallback, base::Unretained(this)), pool, NetLogWithSource())); } void WaitForAuth() { run_loop_ = std::make_unique(); run_loop_->Run(); run_loop_.reset(); } void WaitForAuthAndRestartSync() { restart_sync_ = true; WaitForAuth(); restart_sync_ = false; } void WaitForAuthAndResetHandleSync() { reset_handle_sync_ = true; WaitForAuth(); reset_handle_sync_ = false; } void RestartWithAuth() { DCHECK(restart_with_auth_callback_); std::move(restart_with_auth_callback_).Run(); } int WaitForResult() { int result = callback_.WaitForResult(); // There shouldn't be any callback waiting to be invoked once the request is // complete. EXPECT_FALSE(restart_with_auth_callback_); // The socket should only be initialized on success. EXPECT_EQ(result == OK, handle_.is_initialized()); EXPECT_EQ(result == OK, handle_.socket() != nullptr); return result; } ClientSocketHandle* handle() { return &handle_; } int auth_count() const { return auth_count_; } int have_result() const { return callback_.have_result(); } private: void AuthCallback(const HttpResponseInfo& response, HttpAuthController* auth_controller, base::OnceClosure restart_with_auth_callback) { EXPECT_FALSE(restart_with_auth_callback_); EXPECT_TRUE(restart_with_auth_callback); // Once there's a result, this method shouldn't be invoked again. EXPECT_FALSE(callback_.have_result()); ++auth_count_; run_loop_->Quit(); if (restart_sync_) { std::move(restart_with_auth_callback).Run(); return; } restart_with_auth_callback_ = std::move(restart_with_auth_callback); if (reset_handle_sync_) { handle_.Reset(); return; } } std::unique_ptr run_loop_; base::OnceClosure restart_with_auth_callback_; bool restart_sync_ = false; bool reset_handle_sync_ = false; ClientSocketHandle handle_; int auth_count_ = 0; TestCompletionCallback callback_; }; TEST_F(ClientSocketPoolBaseTest, ProxyAuthOnce) { CreatePool(1, 1); connect_job_factory_->set_job_type(TestConnectJob::kMockAuthChallengeOnceJob); TestAuthHelper auth_helper; auth_helper.InitHandle(params_, pool_.get()); EXPECT_EQ(1u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(LOAD_STATE_CONNECTING, pool_->GetLoadState(TestGroupId("a"), auth_helper.handle())); auth_helper.WaitForAuth(); EXPECT_EQ(1u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(LOAD_STATE_ESTABLISHING_PROXY_TUNNEL, pool_->GetLoadState(TestGroupId("a"), auth_helper.handle())); auth_helper.RestartWithAuth(); EXPECT_EQ(1u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(LOAD_STATE_ESTABLISHING_PROXY_TUNNEL, pool_->GetLoadState(TestGroupId("a"), auth_helper.handle())); EXPECT_THAT(auth_helper.WaitForResult(), IsOk()); EXPECT_EQ(1, auth_helper.auth_count()); EXPECT_EQ(0u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(1, pool_->NumActiveSocketsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->IdleSocketCountInGroup(TestGroupId("a"))); EXPECT_EQ(0, pool_->IdleSocketCount()); } TEST_F(ClientSocketPoolBaseTest, ProxyAuthOnceSync) { CreatePool(1, 1); connect_job_factory_->set_job_type(TestConnectJob::kMockAuthChallengeOnceJob); TestAuthHelper auth_helper; auth_helper.InitHandle(params_, pool_.get()); EXPECT_EQ(1u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(LOAD_STATE_CONNECTING, pool_->GetLoadState(TestGroupId("a"), auth_helper.handle())); auth_helper.WaitForAuthAndRestartSync(); EXPECT_EQ(1u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(LOAD_STATE_ESTABLISHING_PROXY_TUNNEL, pool_->GetLoadState(TestGroupId("a"), auth_helper.handle())); EXPECT_THAT(auth_helper.WaitForResult(), IsOk()); EXPECT_EQ(1, auth_helper.auth_count()); EXPECT_EQ(0u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(1, pool_->NumActiveSocketsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->IdleSocketCountInGroup(TestGroupId("a"))); EXPECT_EQ(0, pool_->IdleSocketCount()); } TEST_F(ClientSocketPoolBaseTest, ProxyAuthOnceFails) { CreatePool(1, 1); connect_job_factory_->set_job_type( TestConnectJob::kMockAuthChallengeOnceFailingJob); TestAuthHelper auth_helper; auth_helper.InitHandle(params_, pool_.get()); EXPECT_EQ(1u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); auth_helper.WaitForAuth(); auth_helper.RestartWithAuth(); EXPECT_THAT(auth_helper.WaitForResult(), IsError(ERR_CONNECTION_FAILED)); EXPECT_EQ(1, auth_helper.auth_count()); EXPECT_FALSE(pool_->HasGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0, pool_->IdleSocketCount()); } TEST_F(ClientSocketPoolBaseTest, ProxyAuthOnceSyncFails) { CreatePool(1, 1); connect_job_factory_->set_job_type( TestConnectJob::kMockAuthChallengeOnceFailingJob); TestAuthHelper auth_helper; auth_helper.InitHandle(params_, pool_.get()); EXPECT_EQ(1u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); auth_helper.WaitForAuthAndRestartSync(); EXPECT_THAT(auth_helper.WaitForResult(), IsError(ERR_CONNECTION_FAILED)); EXPECT_EQ(1, auth_helper.auth_count()); EXPECT_FALSE(pool_->HasGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0, pool_->IdleSocketCount()); } TEST_F(ClientSocketPoolBaseTest, ProxyAuthOnceDeleteHandle) { CreatePool(1, 1); connect_job_factory_->set_job_type(TestConnectJob::kMockAuthChallengeOnceJob); TestAuthHelper auth_helper; auth_helper.InitHandle(params_, pool_.get()); EXPECT_EQ(1u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); auth_helper.WaitForAuth(); EXPECT_EQ(1u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); auth_helper.handle()->Reset(); EXPECT_EQ(1, auth_helper.auth_count()); EXPECT_FALSE(pool_->HasGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0, pool_->IdleSocketCount()); EXPECT_FALSE(auth_helper.handle()->is_initialized()); EXPECT_FALSE(auth_helper.handle()->socket()); } TEST_F(ClientSocketPoolBaseTest, ProxyAuthOnceDeleteHandleSync) { CreatePool(1, 1); connect_job_factory_->set_job_type(TestConnectJob::kMockAuthChallengeOnceJob); TestAuthHelper auth_helper; auth_helper.InitHandle(params_, pool_.get()); EXPECT_EQ(1u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); auth_helper.WaitForAuthAndResetHandleSync(); EXPECT_EQ(1, auth_helper.auth_count()); EXPECT_FALSE(pool_->HasGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0, pool_->IdleSocketCount()); EXPECT_FALSE(auth_helper.handle()->is_initialized()); EXPECT_FALSE(auth_helper.handle()->socket()); } TEST_F(ClientSocketPoolBaseTest, ProxyAuthOnceFlushWithError) { CreatePool(1, 1); connect_job_factory_->set_job_type(TestConnectJob::kMockAuthChallengeOnceJob); TestAuthHelper auth_helper; auth_helper.InitHandle(params_, pool_.get()); EXPECT_EQ(1u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); auth_helper.WaitForAuth(); pool_->FlushWithError(ERR_FAILED, "Network changed"); base::RunLoop().RunUntilIdle(); // When flushing the socket pool, bound sockets should delay returning the // error until completion. EXPECT_FALSE(auth_helper.have_result()); EXPECT_EQ(1u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0, pool_->IdleSocketCount()); auth_helper.RestartWithAuth(); // The callback should be called asynchronously. EXPECT_FALSE(auth_helper.have_result()); EXPECT_THAT(auth_helper.WaitForResult(), IsError(ERR_FAILED)); EXPECT_FALSE(pool_->HasGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0, pool_->IdleSocketCount()); } TEST_F(ClientSocketPoolBaseTest, ProxyAuthTwice) { CreatePool(1, 1); connect_job_factory_->set_job_type( TestConnectJob::kMockAuthChallengeTwiceJob); TestAuthHelper auth_helper; auth_helper.InitHandle(params_, pool_.get()); EXPECT_EQ(1u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(LOAD_STATE_CONNECTING, pool_->GetLoadState(TestGroupId("a"), auth_helper.handle())); auth_helper.WaitForAuth(); auth_helper.RestartWithAuth(); EXPECT_EQ(1u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(1, auth_helper.auth_count()); EXPECT_EQ(LOAD_STATE_ESTABLISHING_PROXY_TUNNEL, pool_->GetLoadState(TestGroupId("a"), auth_helper.handle())); auth_helper.WaitForAuth(); EXPECT_EQ(1u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(2, auth_helper.auth_count()); EXPECT_EQ(LOAD_STATE_ESTABLISHING_PROXY_TUNNEL, pool_->GetLoadState(TestGroupId("a"), auth_helper.handle())); auth_helper.RestartWithAuth(); EXPECT_EQ(1u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(2, auth_helper.auth_count()); EXPECT_EQ(LOAD_STATE_ESTABLISHING_PROXY_TUNNEL, pool_->GetLoadState(TestGroupId("a"), auth_helper.handle())); EXPECT_THAT(auth_helper.WaitForResult(), IsOk()); EXPECT_EQ(2, auth_helper.auth_count()); EXPECT_EQ(0u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(1, pool_->NumActiveSocketsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->IdleSocketCountInGroup(TestGroupId("a"))); EXPECT_EQ(0, pool_->IdleSocketCount()); } TEST_F(ClientSocketPoolBaseTest, ProxyAuthTwiceFails) { CreatePool(1, 1); connect_job_factory_->set_job_type( TestConnectJob::kMockAuthChallengeTwiceFailingJob); TestAuthHelper auth_helper; auth_helper.InitHandle(params_, pool_.get()); EXPECT_EQ(1u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); auth_helper.WaitForAuth(); auth_helper.RestartWithAuth(); EXPECT_EQ(1u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(1, auth_helper.auth_count()); auth_helper.WaitForAuth(); auth_helper.RestartWithAuth(); EXPECT_EQ(1u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(2, auth_helper.auth_count()); EXPECT_THAT(auth_helper.WaitForResult(), IsError(ERR_CONNECTION_FAILED)); EXPECT_EQ(2, auth_helper.auth_count()); EXPECT_FALSE(pool_->HasGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0, pool_->IdleSocketCount()); } // Makes sure that when a bound request is destroyed, a new ConnectJob is // created, if needed. TEST_F(ClientSocketPoolBaseTest, ProxyAuthCreateNewConnectJobOnDestroyBoundRequest) { CreatePool(1 /* max_sockets */, 1 /* max_sockets_per_group */); connect_job_factory_->set_job_type( TestConnectJob::kMockAuthChallengeOnceFailingJob); // First request creates a ConnectJob. TestAuthHelper auth_helper1; auth_helper1.InitHandle(params_, pool_.get()); EXPECT_EQ(1u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); // A second request come in, but no new ConnectJob is needed, since the limit // has been reached. TestAuthHelper auth_helper2; auth_helper2.InitHandle(params_, pool_.get()); EXPECT_EQ(1u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); // Run until the auth callback for the first request is invoked. auth_helper1.WaitForAuth(); EXPECT_EQ(0, auth_helper2.auth_count()); EXPECT_EQ(1u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0, pool_->NumActiveSocketsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->IdleSocketCountInGroup(TestGroupId("a"))); // Make connect jobs succeed, then cancel the first request, which should // destroy the bound ConnectJob, and cause a new ConnectJob to start. connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob); auth_helper1.handle()->Reset(); EXPECT_EQ(0, auth_helper2.auth_count()); EXPECT_EQ(1u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); // The second ConnectJob should succeed. EXPECT_THAT(auth_helper2.WaitForResult(), IsOk()); EXPECT_EQ(0, auth_helper2.auth_count()); EXPECT_EQ(0u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); } // Makes sure that when a bound request is destroyed, a new ConnectJob is // created for another group, if needed. TEST_F(ClientSocketPoolBaseTest, ProxyAuthCreateNewConnectJobOnDestroyBoundRequestDifferentGroups) { CreatePool(1 /* max_sockets */, 1 /* max_sockets_per_group */); connect_job_factory_->set_job_type( TestConnectJob::kMockAuthChallengeOnceFailingJob); // First request creates a ConnectJob. TestAuthHelper auth_helper1; auth_helper1.InitHandle(params_, pool_.get(), DEFAULT_PRIORITY); EXPECT_EQ(1u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); // A second request come in, but no new ConnectJob is needed, since the limit // has been reached. TestAuthHelper auth_helper2; auth_helper2.InitHandle(params_, pool_.get(), DEFAULT_PRIORITY, ClientSocketPool::RespectLimits::ENABLED, TestGroupId("b")); EXPECT_EQ(1u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("b"))); // Run until the auth callback for the first request is invoked. auth_helper1.WaitForAuth(); EXPECT_EQ(0, auth_helper2.auth_count()); EXPECT_EQ(1u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0, pool_->NumActiveSocketsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->IdleSocketCountInGroup(TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("b"))); EXPECT_EQ(0, pool_->NumActiveSocketsInGroupForTesting(TestGroupId("b"))); EXPECT_EQ(0u, pool_->IdleSocketCountInGroup(TestGroupId("b"))); // Make connect jobs succeed, then cancel the first request, which should // destroy the bound ConnectJob, and cause a new ConnectJob to start for the // other group. connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob); auth_helper1.handle()->Reset(); EXPECT_EQ(0, auth_helper2.auth_count()); EXPECT_FALSE(pool_->HasGroupForTesting(TestGroupId("a"))); EXPECT_EQ(1u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("b"))); // The second ConnectJob should succeed. EXPECT_THAT(auth_helper2.WaitForResult(), IsOk()); EXPECT_EQ(0, auth_helper2.auth_count()); EXPECT_FALSE(pool_->HasGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("b"))); } // Test that once an auth challenge is bound, that's the request that gets all // subsequent calls and the socket itself. TEST_F(ClientSocketPoolBaseTest, ProxyAuthStaysBound) { CreatePool(1, 1); connect_job_factory_->set_job_type( TestConnectJob::kMockAuthChallengeTwiceJob); // First request creates a ConnectJob. TestAuthHelper auth_helper1; auth_helper1.InitHandle(params_, pool_.get(), LOWEST); EXPECT_EQ(1u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); // A second, higher priority request is made. TestAuthHelper auth_helper2; auth_helper2.InitHandle(params_, pool_.get(), LOW); EXPECT_EQ(1u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); // Run until the auth callback for the second request is invoked. auth_helper2.WaitForAuth(); EXPECT_EQ(0, auth_helper1.auth_count()); EXPECT_EQ(1u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0, pool_->NumActiveSocketsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0u, pool_->IdleSocketCountInGroup(TestGroupId("a"))); // Start a higher priority job. It shouldn't be able to steal |auth_helper2|'s // ConnectJob. TestAuthHelper auth_helper3; auth_helper3.InitHandle(params_, pool_.get(), HIGHEST); EXPECT_EQ(1u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); // Start a higher job that ignores limits, creating a hanging socket. It // shouldn't be able to steal |auth_helper2|'s ConnectJob. connect_job_factory_->set_job_type(TestConnectJob::kMockWaitingJob); TestAuthHelper auth_helper4; auth_helper4.InitHandle(params_, pool_.get(), HIGHEST, ClientSocketPool::RespectLimits::DISABLED); EXPECT_EQ(2u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); // Restart with auth, and |auth_helper2|'s auth method should be invoked // again. auth_helper2.RestartWithAuth(); auth_helper2.WaitForAuth(); EXPECT_EQ(0, auth_helper1.auth_count()); EXPECT_FALSE(auth_helper1.have_result()); EXPECT_EQ(2, auth_helper2.auth_count()); EXPECT_FALSE(auth_helper2.have_result()); EXPECT_EQ(0, auth_helper3.auth_count()); EXPECT_FALSE(auth_helper3.have_result()); EXPECT_EQ(0, auth_helper4.auth_count()); EXPECT_FALSE(auth_helper4.have_result()); // Advance auth again, and |auth_helper2| should get the socket. auth_helper2.RestartWithAuth(); EXPECT_THAT(auth_helper2.WaitForResult(), IsOk()); // The hung ConnectJob for the RespectLimits::DISABLED request is still in the // socket pool. EXPECT_EQ(1u, pool_->NumConnectJobsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(1, pool_->NumActiveSocketsInGroupForTesting(TestGroupId("a"))); EXPECT_EQ(0, auth_helper1.auth_count()); EXPECT_FALSE(auth_helper1.have_result()); EXPECT_EQ(0, auth_helper3.auth_count()); EXPECT_FALSE(auth_helper3.have_result()); EXPECT_EQ(0, auth_helper4.auth_count()); EXPECT_FALSE(auth_helper4.have_result()); // If the socket is returned to the socket pool, the RespectLimits::DISABLED // socket request should be able to claim it. auth_helper2.handle()->Reset(); EXPECT_THAT(auth_helper4.WaitForResult(), IsOk()); EXPECT_EQ(0, auth_helper1.auth_count()); EXPECT_FALSE(auth_helper1.have_result()); EXPECT_EQ(0, auth_helper3.auth_count()); EXPECT_FALSE(auth_helper3.have_result()); EXPECT_EQ(0, auth_helper4.auth_count()); } enum class RefreshType { kServer, kProxy, }; // Common base class to test RefreshGroup() when called from either // OnSSLConfigForServerChanged() matching a specific group or the pool's proxy. // // Tests which test behavior specific to one or the other case should use // ClientSocketPoolBaseTest directly. In particular, there is no "other group" // when the pool's proxy matches. class ClientSocketPoolBaseRefreshTest : public ClientSocketPoolBaseTest, public testing::WithParamInterface { public: void CreatePoolForRefresh(int max_sockets, int max_sockets_per_group, bool enable_backup_connect_jobs = false) { switch (GetParam()) { case RefreshType::kServer: CreatePool(max_sockets, max_sockets_per_group, enable_backup_connect_jobs); break; case RefreshType::kProxy: CreatePoolWithIdleTimeouts( max_sockets, max_sockets_per_group, kUnusedIdleSocketTimeout, ClientSocketPool::used_idle_socket_timeout(), enable_backup_connect_jobs, PacResultElementToProxyServer("HTTPS myproxy:70")); break; } } static ClientSocketPool::GroupId GetGroupId() { return TestGroupId("a", 443, url::kHttpsScheme); } static ClientSocketPool::GroupId GetGroupIdInPartition() { // Note this GroupId will match GetGroupId() unless // kPartitionConnectionsByNetworkAnonymizationKey is enabled. const SchemefulSite kSite(GURL("https://b/")); const auto kNetworkAnonymizationKey = NetworkAnonymizationKey::CreateSameSite(kSite); return TestGroupId("a", 443, url::kHttpsScheme, PrivacyMode::PRIVACY_MODE_DISABLED, kNetworkAnonymizationKey); } void OnSSLConfigForServerChanged() { switch (GetParam()) { case RefreshType::kServer: pool_->OnSSLConfigForServerChanged(HostPortPair("a", 443)); break; case RefreshType::kProxy: pool_->OnSSLConfigForServerChanged(HostPortPair("myproxy", 70)); break; } } }; INSTANTIATE_TEST_SUITE_P(RefreshType, ClientSocketPoolBaseRefreshTest, ::testing::Values(RefreshType::kServer, RefreshType::kProxy)); TEST_P(ClientSocketPoolBaseRefreshTest, RefreshGroupCreatesNewConnectJobs) { CreatePoolForRefresh(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); const ClientSocketPool::GroupId kGroupId = GetGroupId(); // First job will be waiting until it gets aborted. connect_job_factory_->set_job_type(TestConnectJob::kMockWaitingJob); ClientSocketHandle handle; TestCompletionCallback callback; EXPECT_THAT( handle.Init(kGroupId, params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource()), IsError(ERR_IO_PENDING)); // Switch connect job types, so creating a new ConnectJob will result in // success. connect_job_factory_->set_job_type(TestConnectJob::kMockJob); OnSSLConfigForServerChanged(); EXPECT_EQ(OK, callback.WaitForResult()); ASSERT_TRUE(handle.socket()); EXPECT_EQ(0, pool_->IdleSocketCount()); ASSERT_TRUE(pool_->HasGroupForTesting(kGroupId)); EXPECT_EQ(0u, pool_->IdleSocketCountInGroup(kGroupId)); EXPECT_EQ(0u, pool_->NumConnectJobsInGroupForTesting(kGroupId)); EXPECT_EQ(1, pool_->NumActiveSocketsInGroupForTesting(kGroupId)); } TEST_P(ClientSocketPoolBaseRefreshTest, RefreshGroupClosesIdleConnectJobs) { base::test::ScopedFeatureList feature_list; feature_list.InitAndEnableFeature( features::kPartitionConnectionsByNetworkIsolationKey); CreatePoolForRefresh(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); const ClientSocketPool::GroupId kGroupId = GetGroupId(); const ClientSocketPool::GroupId kGroupIdInPartition = GetGroupIdInPartition(); EXPECT_EQ( OK, pool_->RequestSockets(kGroupId, params_, absl::nullopt, 2, CompletionOnceCallback(), NetLogWithSource())); EXPECT_EQ( OK, pool_->RequestSockets(kGroupIdInPartition, params_, absl::nullopt, 2, CompletionOnceCallback(), NetLogWithSource())); ASSERT_TRUE(pool_->HasGroupForTesting(kGroupId)); ASSERT_TRUE(pool_->HasGroupForTesting(kGroupIdInPartition)); EXPECT_EQ(4, pool_->IdleSocketCount()); EXPECT_EQ(2u, pool_->IdleSocketCountInGroup(kGroupId)); EXPECT_EQ(2u, pool_->IdleSocketCountInGroup(kGroupIdInPartition)); OnSSLConfigForServerChanged(); EXPECT_EQ(0, pool_->IdleSocketCount()); EXPECT_FALSE(pool_->HasGroupForTesting(kGroupId)); EXPECT_FALSE(pool_->HasGroupForTesting(kGroupIdInPartition)); } TEST_F(ClientSocketPoolBaseTest, RefreshGroupDoesNotCloseIdleConnectJobsInOtherGroup) { CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); const ClientSocketPool::GroupId kGroupId = TestGroupId("a", 443, url::kHttpsScheme); const ClientSocketPool::GroupId kOtherGroupId = TestGroupId("b", 443, url::kHttpsScheme); EXPECT_EQ( OK, pool_->RequestSockets(kOtherGroupId, params_, absl::nullopt, 2, CompletionOnceCallback(), NetLogWithSource())); ASSERT_TRUE(pool_->HasGroupForTesting(kOtherGroupId)); EXPECT_EQ(2, pool_->IdleSocketCount()); EXPECT_EQ(2u, pool_->IdleSocketCountInGroup(kOtherGroupId)); pool_->OnSSLConfigForServerChanged(HostPortPair("a", 443)); ASSERT_TRUE(pool_->HasGroupForTesting(kOtherGroupId)); EXPECT_EQ(2, pool_->IdleSocketCount()); EXPECT_EQ(2u, pool_->IdleSocketCountInGroup(kOtherGroupId)); } TEST_P(ClientSocketPoolBaseRefreshTest, RefreshGroupPreventsSocketReuse) { CreatePoolForRefresh(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); const ClientSocketPool::GroupId kGroupId = GetGroupId(); ClientSocketHandle handle; TestCompletionCallback callback; EXPECT_THAT( handle.Init(kGroupId, params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource()), IsOk()); ASSERT_TRUE(pool_->HasGroupForTesting(kGroupId)); EXPECT_EQ(1, pool_->NumActiveSocketsInGroupForTesting(kGroupId)); OnSSLConfigForServerChanged(); handle.Reset(); EXPECT_EQ(0, pool_->IdleSocketCount()); EXPECT_FALSE(pool_->HasGroupForTesting(kGroupId)); } TEST_F(ClientSocketPoolBaseTest, RefreshGroupDoesNotPreventSocketReuseInOtherGroup) { CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); const ClientSocketPool::GroupId kGroupId = TestGroupId("a", 443, url::kHttpsScheme); const ClientSocketPool::GroupId kOtherGroupId = TestGroupId("b", 443, url::kHttpsScheme); ClientSocketHandle handle; TestCompletionCallback callback; EXPECT_THAT( handle.Init(kOtherGroupId, params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource()), IsOk()); ASSERT_TRUE(pool_->HasGroupForTesting(kOtherGroupId)); EXPECT_EQ(1, pool_->NumActiveSocketsInGroupForTesting(kOtherGroupId)); pool_->OnSSLConfigForServerChanged(HostPortPair("a", 443)); handle.Reset(); EXPECT_EQ(1, pool_->IdleSocketCount()); ASSERT_TRUE(pool_->HasGroupForTesting(kOtherGroupId)); EXPECT_EQ(1u, pool_->IdleSocketCountInGroup(kOtherGroupId)); } TEST_P(ClientSocketPoolBaseRefreshTest, RefreshGroupReplacesBoundConnectJobOnConnect) { CreatePoolForRefresh(1, 1); const ClientSocketPool::GroupId kGroupId = GetGroupId(); connect_job_factory_->set_job_type(TestConnectJob::kMockAuthChallengeOnceJob); TestAuthHelper auth_helper; auth_helper.InitHandle(params_, pool_.get(), DEFAULT_PRIORITY, ClientSocketPool::RespectLimits::ENABLED, kGroupId); EXPECT_EQ(1u, pool_->NumConnectJobsInGroupForTesting(kGroupId)); auth_helper.WaitForAuth(); // This should update the generation, but not cancel the old ConnectJob - it's // not safe to do anything while waiting on the original ConnectJob. OnSSLConfigForServerChanged(); // Providing auth credentials and restarting the request with them will cause // the ConnectJob to complete successfully, but the result will be discarded // because of the generation mismatch. auth_helper.RestartWithAuth(); // Despite using ConnectJobs that simulate a single challenge, a second // challenge will be seen, due to using a new ConnectJob. auth_helper.WaitForAuth(); auth_helper.RestartWithAuth(); EXPECT_THAT(auth_helper.WaitForResult(), IsOk()); EXPECT_TRUE(auth_helper.handle()->socket()); EXPECT_EQ(2, auth_helper.auth_count()); // When released, the socket will be returned to the socket pool, and // available for reuse. auth_helper.handle()->Reset(); EXPECT_EQ(1, pool_->IdleSocketCount()); ASSERT_TRUE(pool_->HasGroupForTesting(kGroupId)); EXPECT_EQ(1u, pool_->IdleSocketCountInGroup(kGroupId)); } TEST_F(ClientSocketPoolBaseTest, RefreshProxyRefreshesAllGroups) { CreatePoolWithIdleTimeouts(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup, kUnusedIdleSocketTimeout, ClientSocketPool::used_idle_socket_timeout(), false /* no backup connect jobs */, PacResultElementToProxyServer("HTTPS myproxy:70")); const ClientSocketPool::GroupId kGroupId1 = TestGroupId("a", 443, url::kHttpsScheme); const ClientSocketPool::GroupId kGroupId2 = TestGroupId("b", 443, url::kHttpsScheme); const ClientSocketPool::GroupId kGroupId3 = TestGroupId("c", 443, url::kHttpsScheme); // Make three sockets in three different groups. The third socket is released // to the pool as idle. ClientSocketHandle handle1, handle2, handle3; TestCompletionCallback callback; EXPECT_THAT( handle1.Init(kGroupId1, params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource()), IsOk()); EXPECT_THAT( handle2.Init(kGroupId2, params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource()), IsOk()); EXPECT_THAT( handle3.Init(kGroupId3, params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource()), IsOk()); handle3.Reset(); ASSERT_TRUE(pool_->HasGroupForTesting(kGroupId1)); EXPECT_EQ(1, pool_->NumActiveSocketsInGroupForTesting(kGroupId1)); ASSERT_TRUE(pool_->HasGroupForTesting(kGroupId2)); EXPECT_EQ(1, pool_->NumActiveSocketsInGroupForTesting(kGroupId2)); ASSERT_TRUE(pool_->HasGroupForTesting(kGroupId3)); EXPECT_EQ(1u, pool_->IdleSocketCountInGroup(kGroupId3)); // Changes to some other proxy do not affect the pool. The idle socket remains // alive and closing |handle2| makes the socket available for the pool. pool_->OnSSLConfigForServerChanged(HostPortPair("someotherproxy", 70)); ASSERT_TRUE(pool_->HasGroupForTesting(kGroupId1)); EXPECT_EQ(1, pool_->NumActiveSocketsInGroupForTesting(kGroupId1)); ASSERT_TRUE(pool_->HasGroupForTesting(kGroupId2)); EXPECT_EQ(1, pool_->NumActiveSocketsInGroupForTesting(kGroupId2)); ASSERT_TRUE(pool_->HasGroupForTesting(kGroupId3)); EXPECT_EQ(1u, pool_->IdleSocketCountInGroup(kGroupId3)); handle2.Reset(); ASSERT_TRUE(pool_->HasGroupForTesting(kGroupId2)); EXPECT_EQ(1u, pool_->IdleSocketCountInGroup(kGroupId2)); // Changes to the matching proxy refreshes all groups. pool_->OnSSLConfigForServerChanged(HostPortPair("myproxy", 70)); // Idle sockets are closed. EXPECT_EQ(0, pool_->IdleSocketCount()); EXPECT_FALSE(pool_->HasGroupForTesting(kGroupId2)); EXPECT_FALSE(pool_->HasGroupForTesting(kGroupId3)); // The active socket, however, continues to be active. ASSERT_TRUE(pool_->HasGroupForTesting(kGroupId1)); EXPECT_EQ(1, pool_->NumActiveSocketsInGroupForTesting(kGroupId1)); // Closing it does not make it available for the pool. handle1.Reset(); EXPECT_EQ(0, pool_->IdleSocketCount()); EXPECT_FALSE(pool_->HasGroupForTesting(kGroupId1)); } TEST_F(ClientSocketPoolBaseTest, RefreshBothPrivacyAndNormalSockets) { CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); const ClientSocketPool::GroupId kGroupId = TestGroupId( "a", 443, url::kHttpsScheme, PrivacyMode::PRIVACY_MODE_DISABLED); const ClientSocketPool::GroupId kGroupIdPrivacy = TestGroupId( "a", 443, url::kHttpsScheme, PrivacyMode::PRIVACY_MODE_ENABLED); const ClientSocketPool::GroupId kOtherGroupId = TestGroupId("b", 443, url::kHttpsScheme); // Make a socket in each groups. ClientSocketHandle handle1, handle2, handle3; TestCompletionCallback callback; EXPECT_THAT( handle1.Init(kGroupId, params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource()), IsOk()); EXPECT_THAT( handle2.Init(kGroupIdPrivacy, params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource()), IsOk()); EXPECT_THAT( handle3.Init(kOtherGroupId, params_, absl::nullopt, DEFAULT_PRIORITY, SocketTag(), ClientSocketPool::RespectLimits::ENABLED, callback.callback(), ClientSocketPool::ProxyAuthCallback(), pool_.get(), NetLogWithSource()), IsOk()); ASSERT_TRUE(pool_->HasGroupForTesting(kGroupId)); EXPECT_EQ(1, pool_->NumActiveSocketsInGroupForTesting(kGroupId)); ASSERT_TRUE(pool_->HasGroupForTesting(kGroupIdPrivacy)); EXPECT_EQ(1, pool_->NumActiveSocketsInGroupForTesting(kGroupIdPrivacy)); ASSERT_TRUE(pool_->HasGroupForTesting(kOtherGroupId)); EXPECT_EQ(1, pool_->NumActiveSocketsInGroupForTesting(kOtherGroupId)); pool_->OnSSLConfigForServerChanged(HostPortPair("a", 443)); // Active sockets continue to be active. ASSERT_TRUE(pool_->HasGroupForTesting(kGroupId)); EXPECT_EQ(1, pool_->NumActiveSocketsInGroupForTesting(kGroupId)); ASSERT_TRUE(pool_->HasGroupForTesting(kGroupIdPrivacy)); EXPECT_EQ(1, pool_->NumActiveSocketsInGroupForTesting(kGroupIdPrivacy)); ASSERT_TRUE(pool_->HasGroupForTesting(kOtherGroupId)); EXPECT_EQ(1, pool_->NumActiveSocketsInGroupForTesting(kOtherGroupId)); // Closing them leaves kOtherGroupId alone, but kGroupId and kGroupIdPrivacy // are unusable. handle1.Reset(); handle2.Reset(); handle3.Reset(); EXPECT_EQ(1, pool_->IdleSocketCount()); EXPECT_FALSE(pool_->HasGroupForTesting(kGroupId)); EXPECT_FALSE(pool_->HasGroupForTesting(kGroupIdPrivacy)); EXPECT_TRUE(pool_->HasGroupForTesting(kOtherGroupId)); EXPECT_EQ(1u, pool_->IdleSocketCountInGroup(kOtherGroupId)); } } // namespace } // namespace net