|
|
|
@ -681,64 +681,11 @@ TEST_P(RingHashTest, ContinuesConnectingWithoutPicks) { |
|
|
|
|
hash_policy->mutable_header()->set_header_name("address_hash"); |
|
|
|
|
SetListenerAndRouteConfiguration(balancer_.get(), default_listener_, |
|
|
|
|
new_route_config); |
|
|
|
|
// A connection injector that cancels the RPC after seeing the
|
|
|
|
|
// connection attempt for the non-existant endpoint.
|
|
|
|
|
class ConnectionInjector : public ConnectionAttemptInjector { |
|
|
|
|
public: |
|
|
|
|
explicit ConnectionInjector(int port) : port_(port) {} |
|
|
|
|
|
|
|
|
|
void HandleConnection(grpc_closure* closure, grpc_endpoint** ep, |
|
|
|
|
grpc_pollset_set* interested_parties, |
|
|
|
|
const grpc_channel_args* channel_args, |
|
|
|
|
const grpc_resolved_address* addr, |
|
|
|
|
grpc_core::Timestamp deadline) override { |
|
|
|
|
{ |
|
|
|
|
grpc_core::MutexLock lock(&mu_); |
|
|
|
|
const int port = grpc_sockaddr_get_port(addr); |
|
|
|
|
gpr_log(GPR_INFO, "==> HandleConnection(): seen_port_=%d, port=%d", |
|
|
|
|
seen_port_, port); |
|
|
|
|
if (!seen_port_ && port == port_) { |
|
|
|
|
gpr_log(GPR_INFO, "*** SEEN P0 CONNECTION ATTEMPT"); |
|
|
|
|
queued_p0_attempt_ = absl::make_unique<QueuedAttempt>( |
|
|
|
|
closure, ep, interested_parties, channel_args, addr, deadline); |
|
|
|
|
seen_port_ = true; |
|
|
|
|
cond_.Signal(); |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
AttemptConnection(closure, ep, interested_parties, channel_args, addr, |
|
|
|
|
deadline); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void WaitForP0ConnectionAttempt() { |
|
|
|
|
grpc_core::MutexLock lock(&mu_); |
|
|
|
|
while (!seen_port_) { |
|
|
|
|
cond_.Wait(&mu_); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Invoked by the test when the RPC has been cancelled and it's ready
|
|
|
|
|
// to allow the connection attempt to proceed.
|
|
|
|
|
void CompleteP0ConnectionAttempt() { |
|
|
|
|
grpc_core::ExecCtx exec_ctx; |
|
|
|
|
std::unique_ptr<QueuedAttempt> attempt; |
|
|
|
|
{ |
|
|
|
|
grpc_core::MutexLock lock(&mu_); |
|
|
|
|
attempt = std::move(queued_p0_attempt_); |
|
|
|
|
} |
|
|
|
|
attempt->Resume(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private: |
|
|
|
|
const int port_; |
|
|
|
|
|
|
|
|
|
grpc_core::Mutex mu_; |
|
|
|
|
grpc_core::CondVar cond_; |
|
|
|
|
bool seen_port_ ABSL_GUARDED_BY(mu_) = false; |
|
|
|
|
std::unique_ptr<QueuedAttempt> queued_p0_attempt_ ABSL_GUARDED_BY(mu_); |
|
|
|
|
}; |
|
|
|
|
ConnectionInjector connection_injector(non_existant_endpoint.port); |
|
|
|
|
connection_injector.Start(); |
|
|
|
|
// Start connection attempt injector and add a hold for the P0
|
|
|
|
|
// connection attempt.
|
|
|
|
|
ConnectionHoldInjector injector; |
|
|
|
|
injector.Start(); |
|
|
|
|
auto hold = injector.AddHold(non_existant_endpoint.port); |
|
|
|
|
// A long-running RPC, just used to send the RPC in another thread.
|
|
|
|
|
LongRunningRpc rpc; |
|
|
|
|
std::vector<std::pair<std::string, std::string>> metadata = { |
|
|
|
@ -748,10 +695,10 @@ TEST_P(RingHashTest, ContinuesConnectingWithoutPicks) { |
|
|
|
|
std::move(metadata))); |
|
|
|
|
// Wait for the RPC to trigger the P0 connection attempt, then cancel it,
|
|
|
|
|
// and then allow the connection attempt to complete.
|
|
|
|
|
connection_injector.WaitForP0ConnectionAttempt(); |
|
|
|
|
hold->Wait(); |
|
|
|
|
rpc.CancelRpc(); |
|
|
|
|
EXPECT_EQ(StatusCode::CANCELLED, rpc.GetStatus().error_code()); |
|
|
|
|
connection_injector.CompleteP0ConnectionAttempt(); |
|
|
|
|
hold->Resume(); |
|
|
|
|
// Wait for channel to become connected without any pending RPC.
|
|
|
|
|
EXPECT_TRUE(channel_->WaitForConnected(grpc_timeout_seconds_to_deadline(5))); |
|
|
|
|
// Make sure the backend did not get any requests.
|
|
|
|
@ -781,151 +728,13 @@ TEST_P(RingHashTest, ContinuesConnectingWithoutPicksOneSubchannelAtATime) { |
|
|
|
|
hash_policy->mutable_header()->set_header_name("address_hash"); |
|
|
|
|
SetListenerAndRouteConfiguration(balancer_.get(), default_listener_, |
|
|
|
|
new_route_config); |
|
|
|
|
// A connection injector that ensures that only one subchannel is
|
|
|
|
|
// connecting at a time.
|
|
|
|
|
class ConnectionInjector : public ConnectionAttemptInjector { |
|
|
|
|
public: |
|
|
|
|
ConnectionInjector(int port0, int port1, int port2, int good_port) |
|
|
|
|
: port0_(port0), port1_(port1), port2_(port2), good_port_(good_port) {} |
|
|
|
|
|
|
|
|
|
void HandleConnection(grpc_closure* closure, grpc_endpoint** ep, |
|
|
|
|
grpc_pollset_set* interested_parties, |
|
|
|
|
const grpc_channel_args* channel_args, |
|
|
|
|
const grpc_resolved_address* addr, |
|
|
|
|
grpc_core::Timestamp deadline) override { |
|
|
|
|
{ |
|
|
|
|
grpc_core::MutexLock lock(&mu_); |
|
|
|
|
const int port = grpc_sockaddr_get_port(addr); |
|
|
|
|
gpr_log(GPR_INFO, "==> HandleConnection(): state_=%d, port=%d", state_, |
|
|
|
|
port); |
|
|
|
|
switch (state_) { |
|
|
|
|
case kInit: |
|
|
|
|
EXPECT_NE(port, port1_); |
|
|
|
|
EXPECT_NE(port, port2_); |
|
|
|
|
EXPECT_NE(port, good_port_); |
|
|
|
|
if (port == port0_) { |
|
|
|
|
gpr_log(GPR_INFO, "*** DELAYING ENDPOINT 0"); |
|
|
|
|
new DelayedAttempt(this, closure, ep, interested_parties, |
|
|
|
|
channel_args, addr, deadline); |
|
|
|
|
state_ = kDelayedEndpoint0; |
|
|
|
|
cond_.Signal(); |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
break; |
|
|
|
|
case kResumedEndpoint0: |
|
|
|
|
EXPECT_NE(port, port0_); |
|
|
|
|
EXPECT_NE(port, port2_); |
|
|
|
|
EXPECT_NE(port, good_port_); |
|
|
|
|
if (port == port1_) { |
|
|
|
|
gpr_log(GPR_INFO, "*** DELAYING ENDPOINT 1"); |
|
|
|
|
new DelayedAttempt(this, closure, ep, interested_parties, |
|
|
|
|
channel_args, addr, deadline); |
|
|
|
|
state_ = kDelayedEndpoint1; |
|
|
|
|
return; |
|
|
|
|
} else { |
|
|
|
|
gpr_log(GPR_INFO, "*** UNEXPECTED PORT"); |
|
|
|
|
} |
|
|
|
|
break; |
|
|
|
|
case kResumedEndpoint1: |
|
|
|
|
EXPECT_NE(port, port0_); |
|
|
|
|
EXPECT_NE(port, port1_); |
|
|
|
|
EXPECT_NE(port, good_port_); |
|
|
|
|
if (port == port2_) { |
|
|
|
|
gpr_log(GPR_INFO, "*** DELAYING ENDPOINT 2"); |
|
|
|
|
new DelayedAttempt(this, closure, ep, interested_parties, |
|
|
|
|
channel_args, addr, deadline); |
|
|
|
|
state_ = kDelayedEndpoint2; |
|
|
|
|
return; |
|
|
|
|
} else { |
|
|
|
|
gpr_log(GPR_INFO, "*** UNEXPECTED PORT"); |
|
|
|
|
} |
|
|
|
|
break; |
|
|
|
|
case kResumedEndpoint2: |
|
|
|
|
EXPECT_NE(port, port0_); |
|
|
|
|
EXPECT_NE(port, port1_); |
|
|
|
|
EXPECT_NE(port, port2_); |
|
|
|
|
if (port == good_port_) { |
|
|
|
|
gpr_log(GPR_INFO, "*** DONE WITH ALL UNREACHABLE ENDPOINTS"); |
|
|
|
|
state_ = kDone; |
|
|
|
|
} |
|
|
|
|
break; |
|
|
|
|
case kDelayedEndpoint0: |
|
|
|
|
case kDelayedEndpoint1: |
|
|
|
|
case kDelayedEndpoint2: |
|
|
|
|
ASSERT_THAT(port, ::testing::AllOf(::testing::Ne(port0_), |
|
|
|
|
::testing::Ne(port1_), |
|
|
|
|
::testing::Ne(port2_), |
|
|
|
|
::testing::Ne(good_port_))) |
|
|
|
|
<< "started second connection attempt in parallel"; |
|
|
|
|
break; |
|
|
|
|
case kDone: |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
AttemptConnection(closure, ep, interested_parties, channel_args, addr, |
|
|
|
|
deadline); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void WaitForFirstPortSeen() { |
|
|
|
|
grpc_core::MutexLock lock(&mu_); |
|
|
|
|
while (state_ == kInit) { |
|
|
|
|
cond_.Wait(&mu_); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private: |
|
|
|
|
class DelayedAttempt : public InjectedDelay { |
|
|
|
|
public: |
|
|
|
|
DelayedAttempt(ConnectionInjector* parent, grpc_closure* closure, |
|
|
|
|
grpc_endpoint** ep, grpc_pollset_set* interested_parties, |
|
|
|
|
const grpc_channel_args* channel_args, |
|
|
|
|
const grpc_resolved_address* addr, |
|
|
|
|
grpc_core::Timestamp deadline) |
|
|
|
|
: InjectedDelay( |
|
|
|
|
grpc_core::Duration::Seconds(1 * grpc_test_slowdown_factor()), |
|
|
|
|
closure, ep, interested_parties, channel_args, addr, deadline), |
|
|
|
|
parent_(parent) {} |
|
|
|
|
|
|
|
|
|
private: |
|
|
|
|
void BeforeResumingAction() override { |
|
|
|
|
grpc_core::MutexLock lock(&parent_->mu_); |
|
|
|
|
if (parent_->state_ == kDelayedEndpoint0) { |
|
|
|
|
gpr_log(GPR_INFO, "*** RESUMING ENDPOINT 0"); |
|
|
|
|
parent_->state_ = kResumedEndpoint0; |
|
|
|
|
} else if (parent_->state_ == kDelayedEndpoint1) { |
|
|
|
|
gpr_log(GPR_INFO, "*** RESUMING ENDPOINT 1"); |
|
|
|
|
parent_->state_ = kResumedEndpoint1; |
|
|
|
|
} else if (parent_->state_ == kDelayedEndpoint2) { |
|
|
|
|
gpr_log(GPR_INFO, "*** RESUMING ENDPOINT 2"); |
|
|
|
|
parent_->state_ = kResumedEndpoint2; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
ConnectionInjector* parent_; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
const int port0_; |
|
|
|
|
const int port1_; |
|
|
|
|
const int port2_; |
|
|
|
|
const int good_port_; |
|
|
|
|
|
|
|
|
|
grpc_core::Mutex mu_; |
|
|
|
|
grpc_core::CondVar cond_; |
|
|
|
|
enum { |
|
|
|
|
kInit, |
|
|
|
|
kDelayedEndpoint0, |
|
|
|
|
kResumedEndpoint0, |
|
|
|
|
kDelayedEndpoint1, |
|
|
|
|
kResumedEndpoint1, |
|
|
|
|
kDelayedEndpoint2, |
|
|
|
|
kResumedEndpoint2, |
|
|
|
|
kDone, |
|
|
|
|
} state_ ABSL_GUARDED_BY(mu_) = kInit; |
|
|
|
|
}; |
|
|
|
|
ConnectionInjector connection_injector( |
|
|
|
|
non_existant_endpoint0.port, non_existant_endpoint1.port, |
|
|
|
|
non_existant_endpoint2.port, backends_[0]->port()); |
|
|
|
|
connection_injector.Start(); |
|
|
|
|
// Start connection attempt injector.
|
|
|
|
|
ConnectionHoldInjector injector; |
|
|
|
|
injector.Start(); |
|
|
|
|
auto hold_non_existant0 = injector.AddHold(non_existant_endpoint0.port); |
|
|
|
|
auto hold_non_existant1 = injector.AddHold(non_existant_endpoint1.port); |
|
|
|
|
auto hold_non_existant2 = injector.AddHold(non_existant_endpoint2.port); |
|
|
|
|
auto hold_good = injector.AddHold(backends_[0]->port()); |
|
|
|
|
// A long-running RPC, just used to send the RPC in another thread.
|
|
|
|
|
LongRunningRpc rpc; |
|
|
|
|
std::vector<std::pair<std::string, std::string>> metadata = { |
|
|
|
@ -933,11 +742,48 @@ TEST_P(RingHashTest, ContinuesConnectingWithoutPicksOneSubchannelAtATime) { |
|
|
|
|
non_existant_endpoint0.port)}}; |
|
|
|
|
rpc.StartRpc(stub_.get(), RpcOptions().set_timeout_ms(0).set_metadata( |
|
|
|
|
std::move(metadata))); |
|
|
|
|
// Wait for the RPC to trigger the first connection attempt, then cancel it.
|
|
|
|
|
connection_injector.WaitForFirstPortSeen(); |
|
|
|
|
// Wait for the RPC to trigger a connection attempt to the first address,
|
|
|
|
|
// then cancel the RPC. No other connection attempts should be started yet.
|
|
|
|
|
hold_non_existant0->Wait(); |
|
|
|
|
rpc.CancelRpc(); |
|
|
|
|
EXPECT_FALSE(hold_non_existant1->IsStarted()); |
|
|
|
|
EXPECT_FALSE(hold_non_existant2->IsStarted()); |
|
|
|
|
EXPECT_FALSE(hold_good->IsStarted()); |
|
|
|
|
// Allow the connection attempt to the first address to resume and wait
|
|
|
|
|
// for the attempt for the second address. No other connection
|
|
|
|
|
// attempts should be started yet.
|
|
|
|
|
auto hold_non_existant0_again = injector.AddHold(non_existant_endpoint0.port); |
|
|
|
|
hold_non_existant0->Resume(); |
|
|
|
|
hold_non_existant1->Wait(); |
|
|
|
|
EXPECT_FALSE(hold_non_existant0_again->IsStarted()); |
|
|
|
|
EXPECT_FALSE(hold_non_existant2->IsStarted()); |
|
|
|
|
EXPECT_FALSE(hold_good->IsStarted()); |
|
|
|
|
// Allow the connection attempt to the second address to resume and wait
|
|
|
|
|
// for the attempt for the third address. No other connection
|
|
|
|
|
// attempts should be started yet.
|
|
|
|
|
auto hold_non_existant1_again = injector.AddHold(non_existant_endpoint1.port); |
|
|
|
|
hold_non_existant1->Resume(); |
|
|
|
|
hold_non_existant2->Wait(); |
|
|
|
|
EXPECT_FALSE(hold_non_existant0_again->IsStarted()); |
|
|
|
|
EXPECT_FALSE(hold_non_existant1_again->IsStarted()); |
|
|
|
|
EXPECT_FALSE(hold_good->IsStarted()); |
|
|
|
|
// Allow the connection attempt to the third address to resume and wait
|
|
|
|
|
// for the attempt for the final address. No other connection
|
|
|
|
|
// attempts should be started yet.
|
|
|
|
|
auto hold_non_existant2_again = injector.AddHold(non_existant_endpoint2.port); |
|
|
|
|
hold_non_existant2->Resume(); |
|
|
|
|
hold_good->Wait(); |
|
|
|
|
EXPECT_FALSE(hold_non_existant0_again->IsStarted()); |
|
|
|
|
EXPECT_FALSE(hold_non_existant1_again->IsStarted()); |
|
|
|
|
EXPECT_FALSE(hold_non_existant2_again->IsStarted()); |
|
|
|
|
// Allow the final attempt to resume.
|
|
|
|
|
hold_good->Resume(); |
|
|
|
|
// Wait for channel to become connected without any pending RPC.
|
|
|
|
|
EXPECT_TRUE(channel_->WaitForConnected(grpc_timeout_seconds_to_deadline(10))); |
|
|
|
|
// No other connection attempts should have been started.
|
|
|
|
|
EXPECT_FALSE(hold_non_existant0_again->IsStarted()); |
|
|
|
|
EXPECT_FALSE(hold_non_existant1_again->IsStarted()); |
|
|
|
|
EXPECT_FALSE(hold_non_existant2_again->IsStarted()); |
|
|
|
|
// RPC should have been cancelled.
|
|
|
|
|
EXPECT_EQ(StatusCode::CANCELLED, rpc.GetStatus().error_code()); |
|
|
|
|
// Make sure the backend did not get any requests.
|
|
|
|
|