From e81002cfdac59870de1909617c9cc63d612f2c78 Mon Sep 17 00:00:00 2001 From: "Mark D. Roth" Date: Fri, 10 Mar 2023 09:22:53 -0800 Subject: [PATCH] xDS: fix crash when removing the last endpoint from the last locality in weighted_target (#32571) Fixes #32486. --- .../weighted_target/weighted_target.cc | 4 +- .../end2end/xds/xds_cluster_end2end_test.cc | 42 +++++++++++++++++++ 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/src/core/ext/filters/client_channel/lb_policy/weighted_target/weighted_target.cc b/src/core/ext/filters/client_channel/lb_policy/weighted_target/weighted_target.cc index 6a88cd5c8aa..0eb9db32b35 100644 --- a/src/core/ext/filters/client_channel/lb_policy/weighted_target/weighted_target.cc +++ b/src/core/ext/filters/client_channel/lb_policy/weighted_target/weighted_target.cc @@ -649,8 +649,8 @@ void WeightedTargetLb::WeightedChild::OnConnectivityStateUpdateLocked( state == GRPC_CHANNEL_READY) { connectivity_state_ = state; } - // Notify the LB policy. - weighted_target_policy_->UpdateStateLocked(); + // Update the LB policy's state if this child is not deactivated. + if (weight_ != 0) weighted_target_policy_->UpdateStateLocked(); } void WeightedTargetLb::WeightedChild::DeactivateLocked() { diff --git a/test/cpp/end2end/xds/xds_cluster_end2end_test.cc b/test/cpp/end2end/xds/xds_cluster_end2end_test.cc index 667de8cec32..b10fb854fcc 100644 --- a/test/cpp/end2end/xds/xds_cluster_end2end_test.cc +++ b/test/cpp/end2end/xds/xds_cluster_end2end_test.cc @@ -459,6 +459,48 @@ TEST_P(EdsTest, OneLocalityWithNoEndpoints) { }); } +// This tests the bug described in https://github.com/grpc/grpc/issues/32486. +TEST_P(EdsTest, LocalityBecomesEmptyWithDeactivatedChildStateUpdate) { + CreateAndStartBackends(1); + // Initial EDS resource has one locality with no endpoints. + EdsResourceArgs args({{"locality0", CreateEndpointsForBackends()}}); + balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); + WaitForAllBackends(DEBUG_LOCATION); + // EDS update removes all endpoints from the locality. + EdsResourceArgs::Locality empty_locality("locality0", {}); + args = EdsResourceArgs({std::move(empty_locality)}); + balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); + // Wait for RPCs to start failing. + constexpr char kErrorMessage[] = + "no children in weighted_target policy: " + "EDS resource eds_service_name contains empty localities: " + "\\[\\{region=\"xds_default_locality_region\", " + "zone=\"xds_default_locality_zone\", sub_zone=\"locality0\"\\}\\]"; + SendRpcsUntil(DEBUG_LOCATION, [&](const RpcResult& result) { + if (result.status.ok()) return true; + EXPECT_EQ(result.status.error_code(), StatusCode::UNAVAILABLE); + EXPECT_THAT(result.status.error_message(), + ::testing::MatchesRegex(kErrorMessage)); + return false; + }); + // Shut down backend. This triggers a connectivity state update from the + // deactivated child of the weighted_target policy. + ShutdownAllBackends(); + // Now restart the backend. + StartAllBackends(); + // Re-add endpoint. + args = EdsResourceArgs({{"locality0", CreateEndpointsForBackends()}}); + balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); + // RPCs should eventually succeed. + WaitForAllBackends(DEBUG_LOCATION, 0, 1, [&](const RpcResult& result) { + if (!result.status.ok()) { + EXPECT_EQ(result.status.error_code(), StatusCode::UNAVAILABLE); + EXPECT_THAT(result.status.error_message(), + ::testing::MatchesRegex(kErrorMessage)); + } + }); +} + TEST_P(EdsTest, NoLocalities) { CreateAndStartBackends(1); // Initial EDS resource has no localities.