blob: 968679485aceaf831d09391e347389c0b7840f7e [file] [log] [blame]
// Copyright (c) 2010 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "net/socket/client_socket_pool_base.h"
#include "base/callback.h"
#include "base/compiler_specific.h"
#include "base/message_loop.h"
#include "base/platform_thread.h"
#include "base/ref_counted.h"
#include "base/scoped_vector.h"
#include "base/string_util.h"
#include "net/base/net_log.h"
#include "net/base/net_log_unittest.h"
#include "net/base/net_errors.h"
#include "net/base/request_priority.h"
#include "net/base/test_completion_callback.h"
#include "net/socket/client_socket.h"
#include "net/socket/client_socket_factory.h"
#include "net/socket/client_socket_handle.h"
#include "net/socket/client_socket_pool_histograms.h"
#include "net/socket/socket_test_util.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace net {
namespace {
const int kDefaultMaxSockets = 4;
const int kDefaultMaxSocketsPerGroup = 2;
const net::RequestPriority kDefaultPriority = MEDIUM;
class TestSocketParams : public base::RefCounted<TestSocketParams> {
private:
friend class base::RefCounted<TestSocketParams>;
~TestSocketParams() {}
};
typedef ClientSocketPoolBase<TestSocketParams> TestClientSocketPoolBase;
class MockClientSocket : public ClientSocket {
public:
MockClientSocket() : connected_(false) {}
// Socket methods:
virtual int Read(
IOBuffer* /* buf */, int /* len */, CompletionCallback* /* callback */) {
return ERR_UNEXPECTED;
}
virtual int Write(
IOBuffer* /* buf */, int /* len */, CompletionCallback* /* callback */) {
return ERR_UNEXPECTED;
}
virtual bool SetReceiveBufferSize(int32 size) { return true; }
virtual bool SetSendBufferSize(int32 size) { return true; }
// ClientSocket methods:
virtual int Connect(CompletionCallback* callback) {
connected_ = true;
return OK;
}
virtual void Disconnect() { connected_ = false; }
virtual bool IsConnected() const { return connected_; }
virtual bool IsConnectedAndIdle() const { return connected_; }
virtual int GetPeerAddress(AddressList* /* address */) const {
return ERR_UNEXPECTED;
}
virtual const BoundNetLog& NetLog() const {
return net_log_;
}
private:
bool connected_;
BoundNetLog net_log_;
DISALLOW_COPY_AND_ASSIGN(MockClientSocket);
};
class TestConnectJob;
class MockClientSocketFactory : public ClientSocketFactory {
public:
MockClientSocketFactory() : allocation_count_(0) {}
virtual ClientSocket* CreateTCPClientSocket(const AddressList& addresses,
NetLog* /* net_log */) {
allocation_count_++;
return NULL;
}
virtual SSLClientSocket* CreateSSLClientSocket(
ClientSocketHandle* transport_socket,
const std::string& hostname,
const SSLConfig& ssl_config) {
NOTIMPLEMENTED();
return NULL;
}
void WaitForSignal(TestConnectJob* job) { waiting_jobs_.push_back(job); }
void SignalJobs();
int allocation_count() const { return allocation_count_; }
private:
int allocation_count_;
std::vector<TestConnectJob*> waiting_jobs_;
};
class TestConnectJob : public ConnectJob {
public:
enum JobType {
kMockJob,
kMockFailingJob,
kMockPendingJob,
kMockPendingFailingJob,
kMockWaitingJob,
kMockAdvancingLoadStateJob,
kMockRecoverableJob,
kMockPendingRecoverableJob,
kMockAdditionalErrorStateJob,
kMockPendingAdditionalErrorStateJob,
};
// The kMockPendingJob uses a slight delay before allowing the connect
// to complete.
static const int kPendingConnectDelay = 2;
TestConnectJob(JobType job_type,
const std::string& group_name,
const TestClientSocketPoolBase::Request& request,
base::TimeDelta timeout_duration,
ConnectJob::Delegate* delegate,
MockClientSocketFactory* client_socket_factory,
NetLog* net_log)
: ConnectJob(group_name, timeout_duration, delegate,
BoundNetLog::Make(net_log, NetLog::SOURCE_CONNECT_JOB)),
job_type_(job_type),
client_socket_factory_(client_socket_factory),
method_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)),
load_state_(LOAD_STATE_IDLE),
store_additional_error_state_(false) {}
void Signal() {
DoConnect(waiting_success_, true /* async */, false /* recoverable */);
}
virtual LoadState GetLoadState() const { return load_state_; }
virtual void GetAdditionalErrorState(ClientSocketHandle* handle) {
if (store_additional_error_state_) {
// Set all of the additional error state fields in some way.
handle->set_is_ssl_error(true);
scoped_refptr<HttpResponseHeaders> headers(new HttpResponseHeaders(""));
handle->set_tunnel_auth_response_info(headers, NULL);
}
}
private:
// ConnectJob methods:
virtual int ConnectInternal() {
AddressList ignored;
client_socket_factory_->CreateTCPClientSocket(ignored, NULL);
set_socket(new MockClientSocket());
switch (job_type_) {
case kMockJob:
return DoConnect(true /* successful */, false /* sync */,
false /* recoverable */);
case kMockFailingJob:
return DoConnect(false /* error */, false /* sync */,
false /* recoverable */);
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.
MessageLoop::current()->PostDelayedTask(
FROM_HERE,
method_factory_.NewRunnableMethod(
&TestConnectJob::DoConnect,
true /* successful */,
true /* async */,
false /* recoverable */),
kPendingConnectDelay);
return ERR_IO_PENDING;
case kMockPendingFailingJob:
set_load_state(LOAD_STATE_CONNECTING);
MessageLoop::current()->PostDelayedTask(
FROM_HERE,
method_factory_.NewRunnableMethod(
&TestConnectJob::DoConnect,
false /* error */,
true /* async */,
false /* recoverable */),
2);
return ERR_IO_PENDING;
case kMockWaitingJob:
client_socket_factory_->WaitForSignal(this);
waiting_success_ = true;
return ERR_IO_PENDING;
case kMockAdvancingLoadStateJob:
MessageLoop::current()->PostTask(
FROM_HERE,
method_factory_.NewRunnableMethod(
&TestConnectJob::AdvanceLoadState, load_state_));
return ERR_IO_PENDING;
case kMockRecoverableJob:
return DoConnect(false /* error */, false /* sync */,
true /* recoverable */);
case kMockPendingRecoverableJob:
set_load_state(LOAD_STATE_CONNECTING);
MessageLoop::current()->PostDelayedTask(
FROM_HERE,
method_factory_.NewRunnableMethod(
&TestConnectJob::DoConnect,
false /* error */,
true /* async */,
true /* recoverable */),
2);
return ERR_IO_PENDING;
case kMockAdditionalErrorStateJob:
store_additional_error_state_ = true;
return DoConnect(false /* error */, false /* sync */,
false /* recoverable */);
case kMockPendingAdditionalErrorStateJob:
set_load_state(LOAD_STATE_CONNECTING);
store_additional_error_state_ = true;
MessageLoop::current()->PostDelayedTask(
FROM_HERE,
method_factory_.NewRunnableMethod(
&TestConnectJob::DoConnect,
false /* error */,
true /* async */,
false /* recoverable */),
2);
return ERR_IO_PENDING;
default:
NOTREACHED();
set_socket(NULL);
return ERR_FAILED;
}
}
void set_load_state(LoadState load_state) { load_state_ = load_state; }
int DoConnect(bool succeed, bool was_async, bool recoverable) {
int result = OK;
if (succeed) {
socket()->Connect(NULL);
} else if (recoverable) {
result = ERR_PROXY_AUTH_REQUESTED;
} else {
result = ERR_CONNECTION_FAILED;
set_socket(NULL);
}
if (was_async)
NotifyDelegateOfCompletion(result);
return result;
}
// This function helps simulate the progress of load states on a ConnectJob.
// Each time it is called it advances the load state and posts a task to be
// called again. It stops at the last connecting load state (the one
// before LOAD_STATE_SENDING_REQUEST).
void AdvanceLoadState(LoadState state) {
int tmp = state;
tmp++;
if (tmp < LOAD_STATE_SENDING_REQUEST) {
state = static_cast<LoadState>(tmp);
set_load_state(state);
MessageLoop::current()->PostTask(
FROM_HERE,
method_factory_.NewRunnableMethod(&TestConnectJob::AdvanceLoadState,
state));
}
}
bool waiting_success_;
const JobType job_type_;
MockClientSocketFactory* const client_socket_factory_;
ScopedRunnableMethodFactory<TestConnectJob> method_factory_;
LoadState load_state_;
bool store_additional_error_state_;
DISALLOW_COPY_AND_ASSIGN(TestConnectJob);
};
class TestConnectJobFactory
: public TestClientSocketPoolBase::ConnectJobFactory {
public:
explicit TestConnectJobFactory(MockClientSocketFactory* client_socket_factory)
: job_type_(TestConnectJob::kMockJob),
client_socket_factory_(client_socket_factory) {}
virtual ~TestConnectJobFactory() {}
void set_job_type(TestConnectJob::JobType job_type) { job_type_ = job_type; }
void set_timeout_duration(base::TimeDelta timeout_duration) {
timeout_duration_ = timeout_duration;
}
// ConnectJobFactory methods:
virtual ConnectJob* NewConnectJob(
const std::string& group_name,
const TestClientSocketPoolBase::Request& request,
ConnectJob::Delegate* delegate) const {
return new TestConnectJob(job_type_,
group_name,
request,
timeout_duration_,
delegate,
client_socket_factory_,
NULL);
}
virtual base::TimeDelta ConnectionTimeout() const {
return timeout_duration_;
}
private:
TestConnectJob::JobType job_type_;
base::TimeDelta timeout_duration_;
MockClientSocketFactory* const client_socket_factory_;
DISALLOW_COPY_AND_ASSIGN(TestConnectJobFactory);
};
class TestClientSocketPool : public ClientSocketPool {
public:
TestClientSocketPool(
int max_sockets,
int max_sockets_per_group,
const scoped_refptr<ClientSocketPoolHistograms>& histograms,
base::TimeDelta unused_idle_socket_timeout,
base::TimeDelta used_idle_socket_timeout,
TestClientSocketPoolBase::ConnectJobFactory* connect_job_factory)
: base_(max_sockets, max_sockets_per_group, histograms,
unused_idle_socket_timeout, used_idle_socket_timeout,
connect_job_factory) {}
virtual int RequestSocket(
const std::string& group_name,
const void* params,
net::RequestPriority priority,
ClientSocketHandle* handle,
CompletionCallback* callback,
const BoundNetLog& net_log) {
const scoped_refptr<TestSocketParams>* casted_socket_params =
static_cast<const scoped_refptr<TestSocketParams>*>(params);
return base_.RequestSocket(group_name, *casted_socket_params, priority,
handle, callback, net_log);
}
virtual void CancelRequest(
const std::string& group_name,
ClientSocketHandle* handle) {
base_.CancelRequest(group_name, handle);
}
virtual void ReleaseSocket(
const std::string& group_name,
ClientSocket* socket,
int id) {
base_.ReleaseSocket(group_name, socket, id);
}
virtual void Flush() {
base_.Flush();
}
virtual void CloseIdleSockets() {
base_.CloseIdleSockets();
}
virtual int IdleSocketCount() const { return base_.idle_socket_count(); }
virtual int IdleSocketCountInGroup(const std::string& group_name) const {
return base_.IdleSocketCountInGroup(group_name);
}
virtual LoadState GetLoadState(const std::string& group_name,
const ClientSocketHandle* handle) const {
return base_.GetLoadState(group_name, handle);
}
virtual base::TimeDelta ConnectionTimeout() const {
return base_.ConnectionTimeout();
}
virtual scoped_refptr<ClientSocketPoolHistograms> histograms() const {
return base_.histograms();
}
const TestClientSocketPoolBase* base() const { return &base_; }
int NumConnectJobsInGroup(const std::string& group_name) const {
return base_.NumConnectJobsInGroup(group_name);
}
void CleanupTimedOutIdleSockets() { base_.CleanupIdleSockets(false); }
void EnableBackupJobs() { base_.EnableBackupJobs(); }
private:
~TestClientSocketPool() {}
TestClientSocketPoolBase base_;
DISALLOW_COPY_AND_ASSIGN(TestClientSocketPool);
};
} // namespace
REGISTER_SOCKET_PARAMS_FOR_POOL(TestClientSocketPool, TestSocketParams);
namespace {
void MockClientSocketFactory::SignalJobs() {
for (std::vector<TestConnectJob*>::iterator it = waiting_jobs_.begin();
it != waiting_jobs_.end(); ++it) {
(*it)->Signal();
}
waiting_jobs_.clear();
}
class TestConnectJobDelegate : public ConnectJob::Delegate {
public:
TestConnectJobDelegate()
: have_result_(false), waiting_for_result_(false), result_(OK) {}
virtual ~TestConnectJobDelegate() {}
virtual void OnConnectJobComplete(int result, ConnectJob* job) {
result_ = result;
scoped_ptr<ClientSocket> socket(job->ReleaseSocket());
// socket.get() should be NULL iff result != OK
EXPECT_EQ(socket.get() == NULL, result != OK);
delete job;
have_result_ = true;
if (waiting_for_result_)
MessageLoop::current()->Quit();
}
int WaitForResult() {
DCHECK(!waiting_for_result_);
while (!have_result_) {
waiting_for_result_ = true;
MessageLoop::current()->Run();
waiting_for_result_ = false;
}
have_result_ = false; // auto-reset for next callback
return result_;
}
private:
bool have_result_;
bool waiting_for_result_;
int result_;
};
class ClientSocketPoolBaseTest : public ClientSocketPoolTest {
protected:
ClientSocketPoolBaseTest()
: params_(new TestSocketParams()),
histograms_(new ClientSocketPoolHistograms("ClientSocketPoolTest")) {}
void CreatePool(int max_sockets, int max_sockets_per_group) {
CreatePoolWithIdleTimeouts(
max_sockets,
max_sockets_per_group,
base::TimeDelta::FromSeconds(
ClientSocketPool::unused_idle_socket_timeout()),
base::TimeDelta::FromSeconds(kUsedIdleSocketTimeout));
}
void CreatePoolWithIdleTimeouts(
int max_sockets, int max_sockets_per_group,
base::TimeDelta unused_idle_socket_timeout,
base::TimeDelta used_idle_socket_timeout) {
DCHECK(!pool_.get());
connect_job_factory_ = new TestConnectJobFactory(&client_socket_factory_);
pool_ = new TestClientSocketPool(max_sockets,
max_sockets_per_group,
histograms_,
unused_idle_socket_timeout,
used_idle_socket_timeout,
connect_job_factory_);
}
int StartRequest(const std::string& group_name,
net::RequestPriority priority) {
return StartRequestUsingPool<TestClientSocketPool, TestSocketParams>(
pool_, group_name, priority, params_);
}
virtual void TearDown() {
// 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.
PlatformThread::Sleep(10);
MessageLoop::current()->RunAllPending();
// Need to delete |pool_| before we turn late binding back off. We also need
// to delete |requests_| because the pool is reference counted and requests
// keep reference to it.
// TODO(willchan): Remove this part when late binding becomes the default.
TestClientSocketPool* pool = pool_.get();
pool_ = NULL;
requests_.reset();
pool = NULL;
ClientSocketPoolTest::TearDown();
}
MockClientSocketFactory client_socket_factory_;
TestConnectJobFactory* connect_job_factory_;
scoped_refptr<TestSocketParams> params_;
scoped_refptr<TestClientSocketPool> pool_;
scoped_refptr<ClientSocketPoolHistograms> histograms_;
};
// Even though a timeout is specified, it doesn't time out on a synchronous
// completion.
TEST_F(ClientSocketPoolBaseTest, ConnectJob_NoTimeoutOnSynchronousCompletion) {
TestConnectJobDelegate delegate;
ClientSocketHandle ignored;
TestClientSocketPoolBase::Request request(
&ignored, NULL, kDefaultPriority, params_, BoundNetLog());
scoped_ptr<TestConnectJob> job(
new TestConnectJob(TestConnectJob::kMockJob,
"a",
request,
base::TimeDelta::FromMicroseconds(1),
&delegate,
&client_socket_factory_,
NULL));
EXPECT_EQ(OK, job->Connect());
}
TEST_F(ClientSocketPoolBaseTest, ConnectJob_TimedOut) {
TestConnectJobDelegate delegate;
ClientSocketHandle ignored;
CapturingNetLog log(CapturingNetLog::kUnbounded);
TestClientSocketPoolBase::Request request(
&ignored, NULL, kDefaultPriority, params_, BoundNetLog());
// Deleted by TestConnectJobDelegate.
TestConnectJob* job =
new TestConnectJob(TestConnectJob::kMockPendingJob,
"a",
request,
base::TimeDelta::FromMicroseconds(1),
&delegate,
&client_socket_factory_,
&log);
ASSERT_EQ(ERR_IO_PENDING, job->Connect());
PlatformThread::Sleep(1);
EXPECT_EQ(ERR_TIMED_OUT, delegate.WaitForResult());
EXPECT_EQ(6u, log.entries().size());
EXPECT_TRUE(LogContainsBeginEvent(
log.entries(), 0, NetLog::TYPE_SOCKET_POOL_CONNECT_JOB));
EXPECT_TRUE(LogContainsBeginEvent(
log.entries(), 1, NetLog::TYPE_SOCKET_POOL_CONNECT_JOB_CONNECT));
EXPECT_TRUE(LogContainsEvent(
log.entries(), 2, NetLog::TYPE_CONNECT_JOB_SET_SOCKET,
NetLog::PHASE_NONE));
EXPECT_TRUE(LogContainsEvent(
log.entries(), 3, NetLog::TYPE_SOCKET_POOL_CONNECT_JOB_TIMED_OUT,
NetLog::PHASE_NONE));
EXPECT_TRUE(LogContainsEndEvent(
log.entries(), 4, NetLog::TYPE_SOCKET_POOL_CONNECT_JOB_CONNECT));
EXPECT_TRUE(LogContainsEndEvent(
log.entries(), 5, NetLog::TYPE_SOCKET_POOL_CONNECT_JOB));
}
TEST_F(ClientSocketPoolBaseTest, BasicSynchronous) {
CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup);
TestCompletionCallback callback;
ClientSocketHandle handle;
CapturingBoundNetLog log(CapturingNetLog::kUnbounded);
EXPECT_EQ(OK, handle.Init("a", params_, kDefaultPriority, &callback, pool_,
log.bound()));
EXPECT_TRUE(handle.is_initialized());
EXPECT_TRUE(handle.socket());
handle.Reset();
EXPECT_EQ(4u, log.entries().size());
EXPECT_TRUE(LogContainsBeginEvent(
log.entries(), 0, NetLog::TYPE_SOCKET_POOL));
EXPECT_TRUE(LogContainsEvent(
log.entries(), 1, NetLog::TYPE_SOCKET_POOL_BOUND_TO_CONNECT_JOB,
NetLog::PHASE_NONE));
EXPECT_TRUE(LogContainsEvent(
log.entries(), 2, NetLog::TYPE_SOCKET_POOL_BOUND_TO_SOCKET,
NetLog::PHASE_NONE));
EXPECT_TRUE(LogContainsEndEvent(
log.entries(), 3, NetLog::TYPE_SOCKET_POOL));
}
TEST_F(ClientSocketPoolBaseTest, InitConnectionFailure) {
CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup);
connect_job_factory_->set_job_type(TestConnectJob::kMockFailingJob);
CapturingBoundNetLog log(CapturingNetLog::kUnbounded);
TestSocketRequest req(&request_order_, &completion_count_);
// Set the additional error state members to ensure that they get cleared.
req.handle()->set_is_ssl_error(true);
scoped_refptr<HttpResponseHeaders> headers(new HttpResponseHeaders(""));
req.handle()->set_tunnel_auth_response_info(headers, NULL);
EXPECT_EQ(ERR_CONNECTION_FAILED, req.handle()->Init("a", params_,
kDefaultPriority, &req,
pool_, log.bound()));
EXPECT_FALSE(req.handle()->socket());
EXPECT_FALSE(req.handle()->is_ssl_error());
EXPECT_TRUE(req.handle()->tunnel_auth_response_info().headers.get() == NULL);
EXPECT_EQ(3u, log.entries().size());
EXPECT_TRUE(LogContainsBeginEvent(
log.entries(), 0, NetLog::TYPE_SOCKET_POOL));
EXPECT_TRUE(LogContainsEvent(
log.entries(), 1, NetLog::TYPE_SOCKET_POOL_BOUND_TO_CONNECT_JOB,
NetLog::PHASE_NONE));
EXPECT_TRUE(LogContainsEndEvent(
log.entries(), 2, NetLog::TYPE_SOCKET_POOL));
}
TEST_F(ClientSocketPoolBaseTest, TotalLimit) {
CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup);
// TODO(eroman): Check that the NetLog contains this event.
EXPECT_EQ(OK, StartRequest("a", kDefaultPriority));
EXPECT_EQ(OK, StartRequest("b", kDefaultPriority));
EXPECT_EQ(OK, StartRequest("c", kDefaultPriority));
EXPECT_EQ(OK, StartRequest("d", kDefaultPriority));
EXPECT_EQ(static_cast<int>(requests_.size()),
client_socket_factory_.allocation_count());
EXPECT_EQ(requests_.size() - kDefaultMaxSockets, completion_count_);
EXPECT_EQ(ERR_IO_PENDING, StartRequest("e", kDefaultPriority));
EXPECT_EQ(ERR_IO_PENDING, StartRequest("f", kDefaultPriority));
EXPECT_EQ(ERR_IO_PENDING, StartRequest("g", kDefaultPriority));
ReleaseAllConnections(NO_KEEP_ALIVE);
EXPECT_EQ(static_cast<int>(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(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_EQ(OK, StartRequest("a", kDefaultPriority));
EXPECT_EQ(OK, StartRequest("a", kDefaultPriority));
EXPECT_EQ(OK, StartRequest("b", kDefaultPriority));
EXPECT_EQ(OK, StartRequest("b", kDefaultPriority));
EXPECT_EQ(static_cast<int>(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_EQ(ERR_IO_PENDING, StartRequest("c", kDefaultPriority));
ReleaseAllConnections(NO_KEEP_ALIVE);
EXPECT_EQ(static_cast<int>(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(kIndexOutOfBounds, GetOrderOfRequest(6));
}
TEST_F(ClientSocketPoolBaseTest, TotalLimitRespectsPriority) {
CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup);
EXPECT_EQ(OK, StartRequest("b", LOWEST));
EXPECT_EQ(OK, StartRequest("a", MEDIUM));
EXPECT_EQ(OK, StartRequest("b", HIGHEST));
EXPECT_EQ(OK, StartRequest("a", LOWEST));
EXPECT_EQ(static_cast<int>(requests_.size()),
client_socket_factory_.allocation_count());
EXPECT_EQ(ERR_IO_PENDING, StartRequest("c", LOWEST));
EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", MEDIUM));
EXPECT_EQ(ERR_IO_PENDING, StartRequest("b", HIGHEST));
ReleaseAllConnections(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 ("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(kIndexOutOfBounds, GetOrderOfRequest(8));
}
TEST_F(ClientSocketPoolBaseTest, TotalLimitRespectsGroupLimit) {
CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup);
EXPECT_EQ(OK, StartRequest("a", LOWEST));
EXPECT_EQ(OK, StartRequest("a", LOW));
EXPECT_EQ(OK, StartRequest("b", HIGHEST));
EXPECT_EQ(OK, StartRequest("b", MEDIUM));
EXPECT_EQ(static_cast<int>(requests_.size()),
client_socket_factory_.allocation_count());
EXPECT_EQ(ERR_IO_PENDING, StartRequest("c", MEDIUM));
EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", LOW));
EXPECT_EQ(ERR_IO_PENDING, StartRequest("b", HIGHEST));
ReleaseAllConnections(NO_KEEP_ALIVE);
EXPECT_EQ(static_cast<int>(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(kIndexOutOfBounds, GetOrderOfRequest(8));
}
// Make sure that we count connecting sockets against the total limit.
TEST_F(ClientSocketPoolBaseTest, TotalLimitCountsConnectingSockets) {
CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup);
EXPECT_EQ(OK, StartRequest("a", kDefaultPriority));
EXPECT_EQ(OK, StartRequest("b", kDefaultPriority));
EXPECT_EQ(OK, StartRequest("c", kDefaultPriority));
// Create one asynchronous request.
connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob);
EXPECT_EQ(ERR_IO_PENDING, StartRequest("d", kDefaultPriority));
// 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.
PlatformThread::Sleep(10);
MessageLoop::current()->RunAllPending();
// The next synchronous request should wait for its turn.
connect_job_factory_->set_job_type(TestConnectJob::kMockJob);
EXPECT_EQ(ERR_IO_PENDING, StartRequest("e", kDefaultPriority));
ReleaseAllConnections(NO_KEEP_ALIVE);
EXPECT_EQ(static_cast<int>(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(kIndexOutOfBounds, GetOrderOfRequest(6));
}
TEST_F(ClientSocketPoolBaseTest, CorrectlyCountStalledGroups) {
CreatePool(kDefaultMaxSockets, kDefaultMaxSockets);
connect_job_factory_->set_job_type(TestConnectJob::kMockJob);
EXPECT_EQ(OK, StartRequest("a", kDefaultPriority));
EXPECT_EQ(OK, StartRequest("a", kDefaultPriority));
EXPECT_EQ(OK, StartRequest("a", kDefaultPriority));
EXPECT_EQ(OK, StartRequest("a", kDefaultPriority));
connect_job_factory_->set_job_type(TestConnectJob::kMockWaitingJob);
EXPECT_EQ(kDefaultMaxSockets, client_socket_factory_.allocation_count());
EXPECT_EQ(ERR_IO_PENDING, StartRequest("b", kDefaultPriority));
EXPECT_EQ(ERR_IO_PENDING, StartRequest("c", kDefaultPriority));
EXPECT_EQ(kDefaultMaxSockets, client_socket_factory_.allocation_count());
EXPECT_TRUE(ReleaseOneConnection(KEEP_ALIVE));
EXPECT_EQ(kDefaultMaxSockets + 1, client_socket_factory_.allocation_count());
EXPECT_TRUE(ReleaseOneConnection(KEEP_ALIVE));
EXPECT_EQ(kDefaultMaxSockets + 2, client_socket_factory_.allocation_count());
EXPECT_TRUE(ReleaseOneConnection(KEEP_ALIVE));
EXPECT_TRUE(ReleaseOneConnection(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);
ClientSocketHandle handle;
TestCompletionCallback callback;
EXPECT_EQ(ERR_IO_PENDING, handle.Init("a", params_, kDefaultPriority,
&callback, pool_, BoundNetLog()));
ClientSocketHandle handles[4];
for (size_t i = 0; i < arraysize(handles); ++i) {
TestCompletionCallback callback;
EXPECT_EQ(ERR_IO_PENDING, handles[i].Init("b", params_, kDefaultPriority,
&callback, pool_, BoundNetLog()));
}
// 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 (size_t i = 0; i < arraysize(handles); ++i)
handles[i].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(IntToString(i), params_, kDefaultPriority,
&callbacks[i], pool_, BoundNetLog()));
}
// Force a stalled group.
ClientSocketHandle stalled_handle;
TestCompletionCallback callback;
EXPECT_EQ(ERR_IO_PENDING, stalled_handle.Init("foo", params_,
kDefaultPriority, &callback,
pool_, BoundNetLog()));
// 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(IntToString(i), params_,
kDefaultPriority, &callback,
pool_, BoundNetLog()));
}
// 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("foo", params_,
kDefaultPriority, &callback,
pool_, BoundNetLog()));
// Since it is stalled, it should have no connect jobs.
EXPECT_EQ(0, pool_->NumConnectJobsInGroup("foo"));
// Cancel the stalled request.
handles[0].Reset();
// Now we should have a connect job.
EXPECT_EQ(1, pool_->NumConnectJobsInGroup("foo"));
// The stalled socket should connect.
EXPECT_EQ(OK, callback.WaitForResult());
EXPECT_EQ(kDefaultMaxSockets + 1,
client_socket_factory_.allocation_count());
EXPECT_EQ(0, pool_->IdleSocketCount());
EXPECT_EQ(0, pool_->NumConnectJobsInGroup("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;
{
ClientSocketHandle handles[kDefaultMaxSockets];
for (int i = 0; i < kDefaultMaxSockets; ++i) {
TestCompletionCallback callback;
EXPECT_EQ(OK, handles[i].Init(StringPrintf("Take 2: %d", i), params_,
kDefaultPriority, &callback, pool_,
BoundNetLog()));
}
EXPECT_EQ(kDefaultMaxSockets, client_socket_factory_.allocation_count());
EXPECT_EQ(0, pool_->IdleSocketCount());
// Now we will hit the socket limit.
EXPECT_EQ(ERR_IO_PENDING, stalled_handle.Init("foo", params_,
kDefaultPriority, &callback,
pool_, BoundNetLog()));
// 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_EQ(OK, callback.WaitForResult());
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);
pool_->EnableBackupJobs();
connect_job_factory_->set_job_type(TestConnectJob::kMockJob);
for (int i = 0; i < kDefaultMaxSockets; ++i) {
ClientSocketHandle handle;
TestCompletionCallback callback;
EXPECT_EQ(OK, handle.Init(IntToString(i), params_, kDefaultPriority,
&callback, pool_, BoundNetLog()));
}
// Flush all the DoReleaseSocket tasks.
MessageLoop::current()->RunAllPending();
// 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;
// "0" 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("0", params_, kDefaultPriority, &callback, pool_,
BoundNetLog()));
EXPECT_EQ(kDefaultMaxSockets, client_socket_factory_.allocation_count());
EXPECT_EQ(kDefaultMaxSockets - 1, pool_->IdleSocketCount());
}
TEST_F(ClientSocketPoolBaseTest, PendingRequests) {
CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup);
EXPECT_EQ(OK, StartRequest("a", kDefaultPriority));
EXPECT_EQ(OK, StartRequest("a", kDefaultPriority));
EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", LOWEST));
EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", MEDIUM));
EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", HIGHEST));
EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", LOW));
EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", LOWEST));
ReleaseAllConnections(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(6, GetOrderOfRequest(3));
EXPECT_EQ(4, GetOrderOfRequest(4));
EXPECT_EQ(3, GetOrderOfRequest(5));
EXPECT_EQ(5, GetOrderOfRequest(6));
EXPECT_EQ(7, GetOrderOfRequest(7));
// Make sure we test order of all requests made.
EXPECT_EQ(kIndexOutOfBounds, GetOrderOfRequest(8));
}
TEST_F(ClientSocketPoolBaseTest, PendingRequests_NoKeepAlive) {
CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup);
EXPECT_EQ(OK, StartRequest("a", kDefaultPriority));
EXPECT_EQ(OK, StartRequest("a", kDefaultPriority));
EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", LOWEST));
EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", MEDIUM));
EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", HIGHEST));
EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", LOW));
EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", LOWEST));
ReleaseAllConnections(NO_KEEP_ALIVE);
for (size_t i = kDefaultMaxSocketsPerGroup; i < requests_.size(); ++i)
EXPECT_EQ(OK, requests_[i]->WaitForResult());
EXPECT_EQ(static_cast<int>(requests_.size()),
client_socket_factory_.allocation_count());
EXPECT_EQ(requests_.size() - kDefaultMaxSocketsPerGroup, completion_count_);
}
// This test will start up a RequestSocket() and then immediately Cancel() it.
// The pending connect job will be cancelled and should not call back into
// ClientSocketPoolBase.
TEST_F(ClientSocketPoolBaseTest, CancelRequestClearGroup) {
CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup);
connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob);
TestSocketRequest req(&request_order_, &completion_count_);
EXPECT_EQ(ERR_IO_PENDING, req.handle()->Init("a", params_, kDefaultPriority,
&req, pool_, BoundNetLog()));
req.handle()->Reset();
}
TEST_F(ClientSocketPoolBaseTest, ConnectCancelConnect) {
CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup);
connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob);
ClientSocketHandle handle;
TestCompletionCallback callback;
TestSocketRequest req(&request_order_, &completion_count_);
EXPECT_EQ(ERR_IO_PENDING, handle.Init("a", params_, kDefaultPriority,
&callback, pool_, BoundNetLog()));
handle.Reset();
TestCompletionCallback callback2;
EXPECT_EQ(ERR_IO_PENDING, handle.Init("a", params_, kDefaultPriority,
&callback2, pool_, BoundNetLog()));
EXPECT_EQ(OK, callback2.WaitForResult());
EXPECT_FALSE(callback.have_result());
handle.Reset();
}
TEST_F(ClientSocketPoolBaseTest, CancelRequest) {
CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup);
EXPECT_EQ(OK, StartRequest("a", kDefaultPriority));
EXPECT_EQ(OK, StartRequest("a", kDefaultPriority));
EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", LOWEST));
EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", MEDIUM));
EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", HIGHEST));
EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", LOW));
EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", LOWEST));
// 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(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(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(kIndexOutOfBounds, GetOrderOfRequest(8));
}
class RequestSocketCallback : public CallbackRunner< Tuple1<int> > {
public:
RequestSocketCallback(ClientSocketHandle* handle,
TestClientSocketPool* pool,
TestConnectJobFactory* test_connect_job_factory,
TestConnectJob::JobType next_job_type)
: handle_(handle),
pool_(pool),
within_callback_(false),
test_connect_job_factory_(test_connect_job_factory),
next_job_type_(next_job_type) {}
virtual void RunWithParams(const Tuple1<int>& params) {
callback_.RunWithParams(params);
ASSERT_EQ(OK, params.a);
if (!within_callback_) {
test_connect_job_factory_->set_job_type(next_job_type_);
// Don't allow reuse of the socket. Disconnect it and then release it and
// run through the MessageLoop once to get it completely released.
handle_->socket()->Disconnect();
handle_->Reset();
{
MessageLoop::ScopedNestableTaskAllower nestable(
MessageLoop::current());
MessageLoop::current()->RunAllPending();
}
within_callback_ = true;
TestCompletionCallback next_job_callback;
scoped_refptr<TestSocketParams> params = new TestSocketParams();
int rv = handle_->Init("a", params, kDefaultPriority, &next_job_callback,
pool_, BoundNetLog());
switch (next_job_type_) {
case TestConnectJob::kMockJob:
EXPECT_EQ(OK, rv);
break;
case TestConnectJob::kMockPendingJob:
EXPECT_EQ(ERR_IO_PENDING, rv);
// For pending jobs, wait for new socket to be created. This makes
// sure there are no more pending operations nor any unclosed sockets
// when the test finishes.
// We need to give it a little bit of time to run, so that all the
// operations that happen on timers (e.g. cleanup of idle
// connections) can execute.
{
MessageLoop::ScopedNestableTaskAllower nestable(
MessageLoop::current());
PlatformThread::Sleep(10);
EXPECT_EQ(OK, next_job_callback.WaitForResult());
}
break;
default:
FAIL() << "Unexpected job type: " << next_job_type_;
break;
}
}
}
int WaitForResult() {
return callback_.WaitForResult();
}
private:
ClientSocketHandle* const handle_;
const scoped_refptr<TestClientSocketPool> pool_;
bool within_callback_;
TestConnectJobFactory* const test_connect_job_factory_;
TestConnectJob::JobType next_job_type_;
TestCompletionCallback callback_;
};
TEST_F(ClientSocketPoolBaseTest, RequestPendingJobTwice) {
CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup);
connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob);
ClientSocketHandle handle;
RequestSocketCallback callback(
&handle, pool_.get(), connect_job_factory_,
TestConnectJob::kMockPendingJob);
int rv = handle.Init("a", params_, kDefaultPriority, &callback, pool_,
BoundNetLog());
ASSERT_EQ(ERR_IO_PENDING, rv);
EXPECT_EQ(OK, callback.WaitForResult());
}
TEST_F(ClientSocketPoolBaseTest, RequestPendingJobThenSynchronous) {
CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup);
connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob);
ClientSocketHandle handle;
RequestSocketCallback callback(
&handle, pool_.get(), connect_job_factory_, TestConnectJob::kMockJob);
int rv = handle.Init("a", params_, kDefaultPriority, &callback, pool_,
BoundNetLog());
ASSERT_EQ(ERR_IO_PENDING, rv);
EXPECT_EQ(OK, callback.WaitForResult());
}
// 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_EQ(ERR_IO_PENDING, StartRequest("a", kDefaultPriority));
EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", kDefaultPriority));
EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", kDefaultPriority));
EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", kDefaultPriority));
EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", kDefaultPriority));
EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", kDefaultPriority));
EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", kDefaultPriority));
// Now, kDefaultMaxSocketsPerGroup requests should be active.
// Let's cancel them.
for (int i = 0; i < kDefaultMaxSocketsPerGroup; ++i) {
ASSERT_FALSE(requests_[i]->handle()->is_initialized());
requests_[i]->handle()->Reset();
}
// Let's wait for the rest to complete now.
for (size_t i = kDefaultMaxSocketsPerGroup; i < requests_.size(); ++i) {
EXPECT_EQ(OK, requests_[i]->WaitForResult());
requests_[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_EQ(ERR_IO_PENDING, StartRequest("a", kDefaultPriority));
for (size_t i = 0; i < kNumberOfRequests; ++i)
EXPECT_EQ(ERR_CONNECTION_FAILED, requests_[i]->WaitForResult());
}
TEST_F(ClientSocketPoolBaseTest, CancelActiveRequestThenRequestSocket) {
CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup);
connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob);
TestSocketRequest req(&request_order_, &completion_count_);
int rv = req.handle()->Init("a", params_, kDefaultPriority, &req, pool_,
BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
// Cancel the active request.
req.handle()->Reset();
rv = req.handle()->Init("a", params_, kDefaultPriority, &req, pool_,
BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
EXPECT_EQ(OK, req.WaitForResult());
EXPECT_FALSE(req.handle()->is_reused());
EXPECT_EQ(1U, completion_count_);
EXPECT_EQ(2, client_socket_factory_.allocation_count());
}
// 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_EQ(OK, StartRequest("a", kDefaultPriority));
EXPECT_EQ(OK, StartRequest("a", kDefaultPriority));
// This is going to be a pending request in an otherwise empty group.
EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", kDefaultPriority));
// Reach the maximum socket limit.
EXPECT_EQ(OK, StartRequest("b", kDefaultPriority));
// Create a stalled group with high priorities.
EXPECT_EQ(ERR_IO_PENDING, StartRequest("c", kHighPriority));
EXPECT_EQ(ERR_IO_PENDING, StartRequest("c", kHighPriority));
// Release the first two sockets from "a". Because this is a keepalive,
// the first release will unblock the pending request for "a". The
// second release will unblock a request for "c", becaue it is the next
// high priority socket.
EXPECT_TRUE(ReleaseOneConnection(KEEP_ALIVE));
EXPECT_TRUE(ReleaseOneConnection(KEEP_ALIVE));
// Closing idle sockets should not get us into trouble, but in the bug
// we were hitting a CHECK here.
EXPECT_EQ(0, pool_->IdleSocketCountInGroup("a"));
pool_->CloseIdleSockets();
MessageLoop::current()->RunAllPending(); // Run the released socket wakeups
}
TEST_F(ClientSocketPoolBaseTest, BasicAsynchronous) {
CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup);
connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob);
TestSocketRequest req(&request_order_, &completion_count_);
CapturingBoundNetLog log(CapturingNetLog::kUnbounded);
int rv = req.handle()->Init("a", params_, LOWEST, &req, pool_, log.bound());
EXPECT_EQ(ERR_IO_PENDING, rv);
EXPECT_EQ(LOAD_STATE_CONNECTING, pool_->GetLoadState("a", req.handle()));
EXPECT_EQ(OK, req.WaitForResult());
EXPECT_TRUE(req.handle()->is_initialized());
EXPECT_TRUE(req.handle()->socket());
req.handle()->Reset();
EXPECT_EQ(4u, log.entries().size());
EXPECT_TRUE(LogContainsBeginEvent(
log.entries(), 0, NetLog::TYPE_SOCKET_POOL));
EXPECT_TRUE(LogContainsEvent(
log.entries(), 1, NetLog::TYPE_SOCKET_POOL_BOUND_TO_CONNECT_JOB,
NetLog::PHASE_NONE));
EXPECT_TRUE(LogContainsEvent(
log.entries(), 2, NetLog::TYPE_SOCKET_POOL_BOUND_TO_SOCKET,
NetLog::PHASE_NONE));
EXPECT_TRUE(LogContainsEndEvent(
log.entries(), 3, NetLog::TYPE_SOCKET_POOL));
}
TEST_F(ClientSocketPoolBaseTest,
InitConnectionAsynchronousFailure) {
CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup);
connect_job_factory_->set_job_type(TestConnectJob::kMockPendingFailingJob);
TestSocketRequest req(&request_order_, &completion_count_);
CapturingBoundNetLog log(CapturingNetLog::kUnbounded);
// Set the additional error state members to ensure that they get cleared.
req.handle()->set_is_ssl_error(true);
scoped_refptr<HttpResponseHeaders> headers(new HttpResponseHeaders(""));
req.handle()->set_tunnel_auth_response_info(headers, NULL);
EXPECT_EQ(ERR_IO_PENDING, req.handle()->Init("a", params_, kDefaultPriority,
&req, pool_, log.bound()));
EXPECT_EQ(LOAD_STATE_CONNECTING, pool_->GetLoadState("a", req.handle()));
EXPECT_EQ(ERR_CONNECTION_FAILED, req.WaitForResult());
EXPECT_FALSE(req.handle()->is_ssl_error());
EXPECT_TRUE(req.handle()->tunnel_auth_response_info().headers.get() == NULL);
EXPECT_EQ(3u, log.entries().size());
EXPECT_TRUE(LogContainsBeginEvent(
log.entries(), 0, NetLog::TYPE_SOCKET_POOL));
EXPECT_TRUE(LogContainsEvent(
log.entries(), 1, NetLog::TYPE_SOCKET_POOL_BOUND_TO_CONNECT_JOB,
NetLog::PHASE_NONE));
EXPECT_TRUE(LogContainsEndEvent(
log.entries(), 2, NetLog::TYPE_SOCKET_POOL));
}
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);
TestSocketRequest req(&request_order_, &completion_count_);
TestSocketRequest req2(&request_order_, &completion_count_);
EXPECT_EQ(ERR_IO_PENDING, req.handle()->Init("a", params_, kDefaultPriority,
&req, pool_, BoundNetLog()));
CapturingBoundNetLog log2(CapturingNetLog::kUnbounded);
EXPECT_EQ(ERR_IO_PENDING, req2.handle()->Init("a", params_, kDefaultPriority,
&req2, pool_, BoundNetLog()));
req.handle()->Reset();
// At this point, request 2 is just waiting for the connect job to finish.
EXPECT_EQ(OK, req2.WaitForResult());
req2.handle()->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_EQ(ERR_IO_PENDING, StartRequest("a", LOWEST));
EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", LOW));
EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", MEDIUM));
EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", HIGHEST));
EXPECT_EQ(kDefaultMaxSocketsPerGroup, pool_->NumConnectJobsInGroup("a"));
requests_[2]->handle()->Reset();
requests_[3]->handle()->Reset();
EXPECT_EQ(kDefaultMaxSocketsPerGroup, pool_->NumConnectJobsInGroup("a"));
requests_[1]->handle()->Reset();
EXPECT_EQ(kDefaultMaxSocketsPerGroup, pool_->NumConnectJobsInGroup("a"));
requests_[0]->handle()->Reset();
EXPECT_EQ(kDefaultMaxSocketsPerGroup, pool_->NumConnectJobsInGroup("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);
TestSocketRequest req1(&request_order_, &completion_count_);
int rv = req1.handle()->Init("a", params_, kDefaultPriority, &req1, pool_,
BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
EXPECT_EQ(OK, req1.WaitForResult());
// 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("a", params_, kDefaultPriority, &req2, pool_,
BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
TestSocketRequest req3(&request_order_, &completion_count_);
rv = req3.handle()->Init("a", params_, kDefaultPriority, &req3, pool_,
BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
// 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();
MessageLoop::current()->RunAllPending(); // Run the released socket wakeups
ASSERT_TRUE(req2.handle()->socket());
EXPECT_EQ(OK, req2.WaitForResult());
EXPECT_FALSE(req3.handle()->socket());
// Signal job 2, which should service request 3.
client_socket_factory_.SignalJobs();
EXPECT_EQ(OK, req3.WaitForResult());
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(0, pool_->IdleSocketCountInGroup("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);
TestSocketRequest req1(&request_order_, &completion_count_);
int rv = req1.handle()->Init("a", params_, kDefaultPriority, &req1, pool_,
BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
TestSocketRequest req2(&request_order_, &completion_count_);
rv = req2.handle()->Init("a", params_, kDefaultPriority, &req2, pool_,
BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
// The pending job is sync.
connect_job_factory_->set_job_type(TestConnectJob::kMockJob);
TestSocketRequest req3(&request_order_, &completion_count_);
rv = req3.handle()->Init("a", params_, kDefaultPriority, &req3, pool_,
BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
EXPECT_EQ(ERR_CONNECTION_FAILED, req1.WaitForResult());
EXPECT_EQ(OK, req2.WaitForResult());
EXPECT_EQ(ERR_CONNECTION_FAILED, req3.WaitForResult());
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_F(ClientSocketPoolBaseTest, LoadState) {
CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup);
connect_job_factory_->set_job_type(
TestConnectJob::kMockAdvancingLoadStateJob);
TestSocketRequest req1(&request_order_, &completion_count_);
int rv = req1.handle()->Init("a", params_, kDefaultPriority, &req1, pool_,
BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
EXPECT_EQ(LOAD_STATE_IDLE, req1.handle()->GetLoadState());
MessageLoop::current()->RunAllPending();
TestSocketRequest req2(&request_order_, &completion_count_);
rv = req2.handle()->Init("a", params_, kDefaultPriority, &req2, pool_,
BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
EXPECT_NE(LOAD_STATE_IDLE, req1.handle()->GetLoadState());
EXPECT_NE(LOAD_STATE_IDLE, req2.handle()->GetLoadState());
}
TEST_F(ClientSocketPoolBaseTest, Recoverable) {
CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup);
connect_job_factory_->set_job_type(TestConnectJob::kMockRecoverableJob);
TestSocketRequest req(&request_order_, &completion_count_);
EXPECT_EQ(ERR_PROXY_AUTH_REQUESTED, req.handle()->Init("a", params_,
kDefaultPriority,
&req, pool_,
BoundNetLog()));
EXPECT_TRUE(req.handle()->is_initialized());
EXPECT_TRUE(req.handle()->socket());
req.handle()->Reset();
}
TEST_F(ClientSocketPoolBaseTest, AsyncRecoverable) {
CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup);
connect_job_factory_->set_job_type(
TestConnectJob::kMockPendingRecoverableJob);
TestSocketRequest req(&request_order_, &completion_count_);
EXPECT_EQ(ERR_IO_PENDING, req.handle()->Init("a", params_, kDefaultPriority,
&req, pool_, BoundNetLog()));
EXPECT_EQ(LOAD_STATE_CONNECTING, pool_->GetLoadState("a", req.handle()));
EXPECT_EQ(ERR_PROXY_AUTH_REQUESTED, req.WaitForResult());
EXPECT_TRUE(req.handle()->is_initialized());
EXPECT_TRUE(req.handle()->socket());
req.handle()->Reset();
}
TEST_F(ClientSocketPoolBaseTest, AdditionalErrorStateSynchronous) {
CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup);
connect_job_factory_->set_job_type(
TestConnectJob::kMockAdditionalErrorStateJob);
TestSocketRequest req(&request_order_, &completion_count_);
EXPECT_EQ(ERR_CONNECTION_FAILED, req.handle()->Init("a", params_,
kDefaultPriority, &req,
pool_, BoundNetLog()));
EXPECT_FALSE(req.handle()->is_initialized());
EXPECT_FALSE(req.handle()->socket());
EXPECT_TRUE(req.handle()->is_ssl_error());
EXPECT_FALSE(req.handle()->tunnel_auth_response_info().headers.get() == NULL);
req.handle()->Reset();
}
TEST_F(ClientSocketPoolBaseTest, AdditionalErrorStateAsynchronous) {
CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup);
connect_job_factory_->set_job_type(
TestConnectJob::kMockPendingAdditionalErrorStateJob);
TestSocketRequest req(&request_order_, &completion_count_);
EXPECT_EQ(ERR_IO_PENDING, req.handle()->Init("a", params_, kDefaultPriority,
&req, pool_, BoundNetLog()));
EXPECT_EQ(LOAD_STATE_CONNECTING, pool_->GetLoadState("a", req.handle()));
EXPECT_EQ(ERR_CONNECTION_FAILED, req.WaitForResult());
EXPECT_FALSE(req.handle()->is_initialized());
EXPECT_FALSE(req.handle()->socket());
EXPECT_TRUE(req.handle()->is_ssl_error());
EXPECT_FALSE(req.handle()->tunnel_auth_response_info().headers.get() == NULL);
req.handle()->Reset();
}
TEST_F(ClientSocketPoolBaseTest, CleanupTimedOutIdleSockets) {
CreatePoolWithIdleTimeouts(
kDefaultMaxSockets, kDefaultMaxSocketsPerGroup,
base::TimeDelta(), // Time out unused sockets immediately.
base::TimeDelta::FromDays(1)); // Don't time out used sockets.
connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob);
// Startup two mock pending connect jobs, which will sit in the MessageLoop.
TestSocketRequest req(&request_order_, &completion_count_);
int rv = req.handle()->Init("a", params_, LOWEST, &req, pool_, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
EXPECT_EQ(LOAD_STATE_CONNECTING, pool_->GetLoadState("a", req.handle()));
TestSocketRequest req2(&request_order_, &completion_count_);
rv = req2.handle()->Init("a", params_, LOWEST, &req2, pool_, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
EXPECT_EQ(LOAD_STATE_CONNECTING, pool_->GetLoadState("a", req2.handle()));
// 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).
req.handle()->Reset();
EXPECT_EQ(OK, req2.WaitForResult());
req2.handle()->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.
PlatformThread::Sleep(10);
MessageLoop::current()->RunAllPending();
ASSERT_EQ(2, pool_->IdleSocketCount());
// Invoke the idle socket cleanup check. Only one socket should be left, the
// used socket. Request it to make sure that it's used.
pool_->CleanupTimedOutIdleSockets();
CapturingBoundNetLog log(CapturingNetLog::kUnbounded);
rv = req.handle()->Init("a", params_, LOWEST, &req, pool_, log.bound());
EXPECT_EQ(OK, rv);
EXPECT_TRUE(req.handle()->is_reused());
EXPECT_TRUE(LogContainsEntryWithType(
log.entries(), 1, NetLog::TYPE_SOCKET_POOL_REUSED_AN_EXISTING_SOCKET));
}
// 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::TimeDelta::FromDays(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.
TestSocketRequest req(&request_order_, &completion_count_);
int rv = req.handle()->Init("a", params_, LOWEST, &req, pool_, BoundNetLog());
EXPECT_EQ(OK, rv);
TestSocketRequest req2(&request_order_, &completion_count_);
rv = req2.handle()->Init("a", params_, LOWEST, &req2, pool_, BoundNetLog());
EXPECT_EQ(OK, rv);
TestSocketRequest req3(&request_order_, &completion_count_);
rv = req3.handle()->Init("a", params_, LOWEST, &req3, pool_, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
TestSocketRequest req4(&request_order_, &completion_count_);
rv = req4.handle()->Init("a", params_, LOWEST, &req4, pool_, BoundNetLog());
EXPECT_EQ(ERR_IO_PENDING, rv);
// Release two disconnected sockets.
req.handle()->socket()->Disconnect();
req.handle()->Reset();
req2.handle()->socket()->Disconnect();
req2.handle()->Reset();
EXPECT_EQ(OK, req3.WaitForResult());
EXPECT_FALSE(req3.handle()->is_reused());
EXPECT_EQ(OK, req4.WaitForResult());
EXPECT_FALSE(req4.handle()->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::TimeDelta::FromDays(1)); // Don't time out used sockets.
connect_job_factory_->set_job_type(TestConnectJob::kMockJob);
// Max out the socket limit with 2 per group.
scoped_ptr<TestSocketRequest> req_a[4];
scoped_ptr<TestSocketRequest> req_b[4];
for (int i = 0; i < 2; ++i) {
req_a[i].reset(new TestSocketRequest(&request_order_, &completion_count_));
req_b[i].reset(new TestSocketRequest(&request_order_, &completion_count_));
EXPECT_EQ(OK, req_a[i]->handle()->Init("a", params_, LOWEST, req_a[i].get(),
pool_, BoundNetLog()));
EXPECT_EQ(OK, req_b[i]->handle()->Init("b", params_, LOWEST, req_b[i].get(),
pool_, BoundNetLog()));
}
// Make 4 pending requests, 2 per group.
for (int i = 2; i < 4; ++i) {
req_a[i].reset(new TestSocketRequest(&request_order_, &completion_count_));
req_b[i].reset(new TestSocketRequest(&request_order_, &completion_count_));
EXPECT_EQ(ERR_IO_PENDING, req_a[i]->handle()->Init("a", params_, LOWEST,
req_a[i].get(), pool_,
BoundNetLog()));
EXPECT_EQ(ERR_IO_PENDING, req_b[i]->handle()->Init("b", params_, LOWEST,
req_b[i].get(), pool_,
BoundNetLog()));
}
// 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.
req_b[0]->handle()->socket()->Disconnect();
req_b[0]->handle()->Reset();
req_a[0]->handle()->socket()->Disconnect();
req_a[0]->handle()->Reset();
// Used to get stuck here.
MessageLoop::current()->RunAllPending();
req_b[1]->handle()->socket()->Disconnect();
req_b[1]->handle()->Reset();
req_a[1]->handle()->socket()->Disconnect();
req_a[1]->handle()->Reset();
for (int i = 2; i < 4; ++i) {
EXPECT_EQ(OK, req_b[i]->WaitForResult());
EXPECT_EQ(OK, req_a[i]->WaitForResult());
}
}
TEST_F(ClientSocketPoolBaseTest,
ReleasingDisconnectedSocketsMaintainsPriorityOrder) {
CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup);
connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob);
EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", kDefaultPriority));
EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", kDefaultPriority));
EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", kDefaultPriority));
EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", kDefaultPriority));
EXPECT_EQ(OK, requests_[0]->WaitForResult());
EXPECT_EQ(OK, requests_[1]->WaitForResult());
EXPECT_EQ(2u, completion_count_);
// Releases one connection.
EXPECT_TRUE(ReleaseOneConnection(NO_KEEP_ALIVE));
EXPECT_EQ(OK, requests_[2]->WaitForResult());
EXPECT_TRUE(ReleaseOneConnection(NO_KEEP_ALIVE));
EXPECT_EQ(OK, requests_[3]->WaitForResult());
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(kIndexOutOfBounds, GetOrderOfRequest(5));
}
class TestReleasingSocketRequest : public CallbackRunner< Tuple1<int> > {
public:
TestReleasingSocketRequest(TestClientSocketPool* pool, int expected_result,
bool reset_releasing_handle)
: pool_(pool),
expected_result_(expected_result),
reset_releasing_handle_(reset_releasing_handle) {}
ClientSocketHandle* handle() { return &handle_; }
int WaitForResult() {
return callback_.WaitForResult();
}
virtual void RunWithParams(const Tuple1<int>& params) {
callback_.RunWithParams(params);
if (reset_releasing_handle_)
handle_.Reset();
scoped_refptr<TestSocketParams> con_params = new TestSocketParams();
EXPECT_EQ(expected_result_, handle2_.Init("a", con_params, kDefaultPriority,
&callback2_, pool_,
BoundNetLog()));
}
private:
scoped_refptr<TestClientSocketPool> pool_;
int expected_result_;
bool reset_releasing_handle_;
ClientSocketHandle handle_;
ClientSocketHandle handle2_;
TestCompletionCallback callback_;
TestCompletionCallback callback2_;
};
TEST_F(ClientSocketPoolBaseTest, AdditionalErrorSocketsDontUseSlot) {
CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup);
EXPECT_EQ(OK, StartRequest("b", kDefaultPriority));
EXPECT_EQ(OK, StartRequest("a", kDefaultPriority));
EXPECT_EQ(OK, StartRequest("b", kDefaultPriority));
EXPECT_EQ(static_cast<int>(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("a", params_, kDefaultPriority,
&req, pool_, BoundNetLog()));
// The next job should complete synchronously
connect_job_factory_->set_job_type(TestConnectJob::kMockJob);
EXPECT_EQ(ERR_CONNECTION_FAILED, req.WaitForResult());
EXPECT_FALSE(req.handle()->is_initialized());
EXPECT_FALSE(req.handle()->socket());
EXPECT_TRUE(req.handle()->is_ssl_error());
EXPECT_FALSE(req.handle()->tunnel_auth_response_info().headers.get() == NULL);
}
// 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("a", params_, kDefaultPriority,
&callback, pool_, BoundNetLog()));
// Simulate flushing the pool.
pool_ = NULL;
// 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("a", params_, kDefaultPriority,
&callback, pool_, BoundNetLog()));
EXPECT_EQ(OK, callback.WaitForResult());
EXPECT_EQ(ClientSocketHandle::UNUSED, handle.reuse_type());
pool_->Flush();
handle.Reset();
MessageLoop::current()->RunAllPending();
EXPECT_EQ(ERR_IO_PENDING, handle.Init("a", params_, kDefaultPriority,
&callback, pool_, BoundNetLog()));
EXPECT_EQ(OK, callback.WaitForResult());
EXPECT_EQ(ClientSocketHandle::UNUSED, handle.reuse_type());
}
// 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);
pool_->EnableBackupJobs();
// Create the first socket and set to ERR_IO_PENDING. This creates a
// backup job.
connect_job_factory_->set_job_type(TestConnectJob::kMockWaitingJob);
ClientSocketHandle handle;
TestCompletionCallback callback;
EXPECT_EQ(ERR_IO_PENDING, handle.Init("bar", params_, kDefaultPriority,
&callback, pool_, BoundNetLog()));
// 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) {
TestCompletionCallback callback;
EXPECT_EQ(OK, handles[i].Init("bar", params_, kDefaultPriority, &callback,
pool_, BoundNetLog()));
}
MessageLoop::current()->RunAllPending();
// Cancel the pending request.
handle.Reset();
// Wait for the backup timer to fire (add some slop to ensure it fires)
PlatformThread::Sleep(ClientSocketPool::kMaxConnectRetryIntervalMs / 2 * 3);
MessageLoop::current()->RunAllPending();
EXPECT_EQ(kDefaultMaxSockets, client_socket_factory_.allocation_count());
}
// 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("a", params_, kDefaultPriority,
&callback, pool_, BoundNetLog()));
EXPECT_EQ(OK, callback.WaitForResult());
// No idle sockets, no pending jobs.
EXPECT_EQ(0, pool_->IdleSocketCount());
EXPECT_EQ(0, pool_->NumConnectJobsInGroup("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("a", params_, kDefaultPriority,
&callback, pool_, BoundNetLog()));
// No idle sockets, and one connecting job.
EXPECT_EQ(0, pool_->IdleSocketCount());
EXPECT_EQ(1, pool_->NumConnectJobsInGroup("a"));
// Return the first handle to the pool. This will initiate the delayed
// binding.
handle1.Reset();
MessageLoop::current()->RunAllPending();
// Still no idle sockets, still one pending connect job.
EXPECT_EQ(0, pool_->IdleSocketCount());
EXPECT_EQ(1, pool_->NumConnectJobsInGroup("a"));
// The second socket connected, even though it was a Waiting Job.
EXPECT_EQ(OK, callback.WaitForResult());
// And we can see there is still one job waiting.
EXPECT_EQ(1, pool_->NumConnectJobsInGroup("a"));
// Finally, signal the waiting Connect.
client_socket_factory_.SignalJobs();
EXPECT_EQ(0, pool_->NumConnectJobsInGroup("a"));
MessageLoop::current()->RunAllPending();
}
// 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("a", params_, kDefaultPriority,
&callback, pool_, BoundNetLog()));
EXPECT_EQ(OK, callback.WaitForResult());
// No idle sockets, no pending jobs.
EXPECT_EQ(0, pool_->IdleSocketCount());
EXPECT_EQ(0, pool_->NumConnectJobsInGroup("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("a", params_, kDefaultPriority,
&callback, pool_, BoundNetLog()));
// No idle sockets, and one connecting job.
EXPECT_EQ(0, pool_->IdleSocketCount());
EXPECT_EQ(1, pool_->NumConnectJobsInGroup("a"));
// Return the first handle to the pool. This will initiate the delayed
// binding.
handle1.Reset();
MessageLoop::current()->RunAllPending();
// Still no idle sockets, still one pending connect job.
EXPECT_EQ(0, pool_->IdleSocketCount());
EXPECT_EQ(1, pool_->NumConnectJobsInGroup("a"));
// The second socket connected, even though it was a Waiting Job.
EXPECT_EQ(OK, callback.WaitForResult());
// And we can see there is still one job waiting.
EXPECT_EQ(1, pool_->NumConnectJobsInGroup("a"));
// Finally, signal the waiting Connect.
client_socket_factory_.SignalJobs();
EXPECT_EQ(0, pool_->NumConnectJobsInGroup("a"));
MessageLoop::current()->RunAllPending();
}
// 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, th 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("a", params_, kDefaultPriority,
&callback, pool_, BoundNetLog()));
EXPECT_EQ(OK, callback.WaitForResult());
// No idle sockets, no pending jobs.
EXPECT_EQ(0, pool_->IdleSocketCount());
EXPECT_EQ(0, pool_->NumConnectJobsInGroup("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("a", params_, kDefaultPriority,
&callback, pool_, BoundNetLog()));
// No idle sockets, and one connecting job.
EXPECT_EQ(0, pool_->IdleSocketCount());
EXPECT_EQ(1, pool_->NumConnectJobsInGroup("a"));
// Return the first handle to the pool. This will initiate the delayed
// binding.
handle1.Reset();
MessageLoop::current()->RunAllPending();
// Still no idle sockets, still one pending connect job.
EXPECT_EQ(0, pool_->IdleSocketCount());
EXPECT_EQ(1, pool_->NumConnectJobsInGroup("a"));
// The second socket connected, even though it was a Waiting Job.
EXPECT_EQ(OK, callback.WaitForResult());
// And we can see there is still one job waiting.
EXPECT_EQ(1, pool_->NumConnectJobsInGroup("a"));
// Finally, signal the waiting Connect.
client_socket_factory_.SignalJobs();
EXPECT_EQ(0, pool_->NumConnectJobsInGroup("a"));
MessageLoop::current()->RunAllPending();
}
} // namespace
} // namespace net