|  | @@ -144,14 +144,14 @@ class XdsClient::ChannelState::AdsCallState
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      void Orphan() override {
 | 
	
		
			
				|  |  |        Finish();
 | 
	
		
			
				|  |  | -      Unref();
 | 
	
		
			
				|  |  | +      Unref(DEBUG_LOCATION, "Orphan");
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      void Start(RefCountedPtr<AdsCallState> ads_calld) {
 | 
	
		
			
				|  |  |        if (sent_) return;
 | 
	
		
			
				|  |  |        sent_ = true;
 | 
	
		
			
				|  |  |        ads_calld_ = std::move(ads_calld);
 | 
	
		
			
				|  |  | -      Ref().release();
 | 
	
		
			
				|  |  | +      Ref(DEBUG_LOCATION, "timer").release();
 | 
	
		
			
				|  |  |        timer_pending_ = true;
 | 
	
		
			
				|  |  |        grpc_timer_init(
 | 
	
		
			
				|  |  |            &timer_,
 | 
	
	
		
			
				|  | @@ -186,27 +186,34 @@ class XdsClient::ChannelState::AdsCallState
 | 
	
		
			
				|  |  |            gpr_log(GPR_INFO, "[xds_client %p] %s", ads_calld_->xds_client(),
 | 
	
		
			
				|  |  |                    grpc_error_string(watcher_error));
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  | -        if (type_url_ == XdsApi::kLdsTypeUrl ||
 | 
	
		
			
				|  |  | -            type_url_ == XdsApi::kRdsTypeUrl) {
 | 
	
		
			
				|  |  | -          ads_calld_->xds_client()->listener_watcher_->OnError(watcher_error);
 | 
	
		
			
				|  |  | +        if (type_url_ == XdsApi::kLdsTypeUrl) {
 | 
	
		
			
				|  |  | +          ListenerState& state = ads_calld_->xds_client()->listener_map_[name_];
 | 
	
		
			
				|  |  | +          for (const auto& p : state.watchers) {
 | 
	
		
			
				|  |  | +            p.first->OnError(GRPC_ERROR_REF(watcher_error));
 | 
	
		
			
				|  |  | +          }
 | 
	
		
			
				|  |  | +        } else if (type_url_ == XdsApi::kRdsTypeUrl) {
 | 
	
		
			
				|  |  | +          RouteConfigState& state =
 | 
	
		
			
				|  |  | +              ads_calld_->xds_client()->route_config_map_[name_];
 | 
	
		
			
				|  |  | +          for (const auto& p : state.watchers) {
 | 
	
		
			
				|  |  | +            p.first->OnError(GRPC_ERROR_REF(watcher_error));
 | 
	
		
			
				|  |  | +          }
 | 
	
		
			
				|  |  |          } else if (type_url_ == XdsApi::kCdsTypeUrl) {
 | 
	
		
			
				|  |  |            ClusterState& state = ads_calld_->xds_client()->cluster_map_[name_];
 | 
	
		
			
				|  |  |            for (const auto& p : state.watchers) {
 | 
	
		
			
				|  |  |              p.first->OnError(GRPC_ERROR_REF(watcher_error));
 | 
	
		
			
				|  |  |            }
 | 
	
		
			
				|  |  | -          GRPC_ERROR_UNREF(watcher_error);
 | 
	
		
			
				|  |  |          } else if (type_url_ == XdsApi::kEdsTypeUrl) {
 | 
	
		
			
				|  |  |            EndpointState& state = ads_calld_->xds_client()->endpoint_map_[name_];
 | 
	
		
			
				|  |  |            for (const auto& p : state.watchers) {
 | 
	
		
			
				|  |  |              p.first->OnError(GRPC_ERROR_REF(watcher_error));
 | 
	
		
			
				|  |  |            }
 | 
	
		
			
				|  |  | -          GRPC_ERROR_UNREF(watcher_error);
 | 
	
		
			
				|  |  |          } else {
 | 
	
		
			
				|  |  |            GPR_UNREACHABLE_CODE(return );
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  | +        GRPC_ERROR_UNREF(watcher_error);
 | 
	
		
			
				|  |  |        }
 | 
	
		
			
				|  |  |        ads_calld_.reset();
 | 
	
		
			
				|  |  | -      Unref();
 | 
	
		
			
				|  |  | +      Unref(DEBUG_LOCATION, "timer");
 | 
	
		
			
				|  |  |        GRPC_ERROR_UNREF(error);
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -235,8 +242,8 @@ class XdsClient::ChannelState::AdsCallState
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |    void SendMessageLocked(const std::string& type_url);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -  void AcceptLdsUpdate(absl::optional<XdsApi::LdsUpdate> lds_update);
 | 
	
		
			
				|  |  | -  void AcceptRdsUpdate(absl::optional<XdsApi::RdsUpdate> rds_update);
 | 
	
		
			
				|  |  | +  void AcceptLdsUpdate(XdsApi::LdsUpdateMap lds_update_map);
 | 
	
		
			
				|  |  | +  void AcceptRdsUpdate(XdsApi::RdsUpdateMap rds_update_map);
 | 
	
		
			
				|  |  |    void AcceptCdsUpdate(XdsApi::CdsUpdateMap cds_update_map);
 | 
	
		
			
				|  |  |    void AcceptEdsUpdate(XdsApi::EdsUpdateMap eds_update_map);
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -489,6 +496,7 @@ XdsClient::ChannelState::~ChannelState() {
 | 
	
		
			
				|  |  |              this);
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  |    grpc_channel_destroy(channel_);
 | 
	
		
			
				|  |  | +  xds_client_.reset(DEBUG_LOCATION, "ChannelState");
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  void XdsClient::ChannelState::Orphan() {
 | 
	
	
		
			
				|  | @@ -525,7 +533,7 @@ void XdsClient::ChannelState::StartConnectivityWatchLocked() {
 | 
	
		
			
				|  |  |    grpc_channel_element* client_channel_elem =
 | 
	
		
			
				|  |  |        grpc_channel_stack_last_element(grpc_channel_get_channel_stack(channel_));
 | 
	
		
			
				|  |  |    GPR_ASSERT(client_channel_elem->filter == &grpc_client_channel_filter);
 | 
	
		
			
				|  |  | -  watcher_ = new StateWatcher(Ref());
 | 
	
		
			
				|  |  | +  watcher_ = new StateWatcher(Ref(DEBUG_LOCATION, "ChannelState+watch"));
 | 
	
		
			
				|  |  |    grpc_client_channel_start_connectivity_watch(
 | 
	
		
			
				|  |  |        client_channel_elem, GRPC_CHANNEL_IDLE,
 | 
	
		
			
				|  |  |        OrphanablePtr<AsyncConnectivityStateWatcherInterface>(watcher_));
 | 
	
	
		
			
				|  | @@ -560,8 +568,11 @@ void XdsClient::ChannelState::Unsubscribe(const std::string& type_url,
 | 
	
		
			
				|  |  |                                            const std::string& name,
 | 
	
		
			
				|  |  |                                            bool delay_unsubscription) {
 | 
	
		
			
				|  |  |    if (ads_calld_ != nullptr) {
 | 
	
		
			
				|  |  | -    ads_calld_->calld()->Unsubscribe(type_url, name, delay_unsubscription);
 | 
	
		
			
				|  |  | -    if (!ads_calld_->calld()->HasSubscribedResources()) ads_calld_.reset();
 | 
	
		
			
				|  |  | +    auto* calld = ads_calld_->calld();
 | 
	
		
			
				|  |  | +    if (calld != nullptr) {
 | 
	
		
			
				|  |  | +      calld->Unsubscribe(type_url, name, delay_unsubscription);
 | 
	
		
			
				|  |  | +      if (!calld->HasSubscribedResources()) ads_calld_.reset();
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -678,7 +689,6 @@ XdsClient::ChannelState::AdsCallState::AdsCallState(
 | 
	
		
			
				|  |  |    // activity in xds_client()->interested_parties_, which is comprised of
 | 
	
		
			
				|  |  |    // the polling entities from client_channel.
 | 
	
		
			
				|  |  |    GPR_ASSERT(xds_client() != nullptr);
 | 
	
		
			
				|  |  | -  GPR_ASSERT(!xds_client()->server_name_.empty());
 | 
	
		
			
				|  |  |    // Create a call with the specified method name.
 | 
	
		
			
				|  |  |    const auto& method =
 | 
	
		
			
				|  |  |        xds_client()->bootstrap_->server().ShouldUseV3()
 | 
	
	
		
			
				|  | @@ -717,13 +727,11 @@ XdsClient::ChannelState::AdsCallState::AdsCallState(
 | 
	
		
			
				|  |  |    // Op: send request message.
 | 
	
		
			
				|  |  |    GRPC_CLOSURE_INIT(&on_request_sent_, OnRequestSent, this,
 | 
	
		
			
				|  |  |                      grpc_schedule_on_exec_ctx);
 | 
	
		
			
				|  |  | -  if (xds_client()->listener_watcher_ != nullptr) {
 | 
	
		
			
				|  |  | -    Subscribe(XdsApi::kLdsTypeUrl, xds_client()->server_name_);
 | 
	
		
			
				|  |  | -    if (xds_client()->lds_result_.has_value() &&
 | 
	
		
			
				|  |  | -        !xds_client()->lds_result_->route_config_name.empty()) {
 | 
	
		
			
				|  |  | -      Subscribe(XdsApi::kRdsTypeUrl,
 | 
	
		
			
				|  |  | -                xds_client()->lds_result_->route_config_name);
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | +  for (const auto& p : xds_client()->listener_map_) {
 | 
	
		
			
				|  |  | +    Subscribe(XdsApi::kLdsTypeUrl, std::string(p.first));
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  | +  for (const auto& p : xds_client()->route_config_map_) {
 | 
	
		
			
				|  |  | +    Subscribe(XdsApi::kRdsTypeUrl, std::string(p.first));
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  |    for (const auto& p : xds_client()->cluster_map_) {
 | 
	
		
			
				|  |  |      Subscribe(XdsApi::kCdsTypeUrl, std::string(p.first));
 | 
	
	
		
			
				|  | @@ -867,113 +875,128 @@ bool XdsClient::ChannelState::AdsCallState::HasSubscribedResources() const {
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  void XdsClient::ChannelState::AdsCallState::AcceptLdsUpdate(
 | 
	
		
			
				|  |  | -    absl::optional<XdsApi::LdsUpdate> lds_update) {
 | 
	
		
			
				|  |  | -  if (!lds_update.has_value()) {
 | 
	
		
			
				|  |  | -    gpr_log(GPR_INFO,
 | 
	
		
			
				|  |  | -            "[xds_client %p] LDS update does not include requested resource",
 | 
	
		
			
				|  |  | -            xds_client());
 | 
	
		
			
				|  |  | -    if (xds_client()->lds_result_.has_value() &&
 | 
	
		
			
				|  |  | -        !xds_client()->lds_result_->route_config_name.empty()) {
 | 
	
		
			
				|  |  | -      Unsubscribe(XdsApi::kRdsTypeUrl,
 | 
	
		
			
				|  |  | -                  xds_client()->lds_result_->route_config_name,
 | 
	
		
			
				|  |  | -                  /*delay_unsubscription=*/false);
 | 
	
		
			
				|  |  | -      xds_client()->rds_result_.reset();
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -    xds_client()->lds_result_.reset();
 | 
	
		
			
				|  |  | -    xds_client()->listener_watcher_->OnResourceDoesNotExist();
 | 
	
		
			
				|  |  | -    return;
 | 
	
		
			
				|  |  | -  }
 | 
	
		
			
				|  |  | +    XdsApi::LdsUpdateMap lds_update_map) {
 | 
	
		
			
				|  |  |    if (GRPC_TRACE_FLAG_ENABLED(grpc_xds_client_trace)) {
 | 
	
		
			
				|  |  |      gpr_log(GPR_INFO,
 | 
	
		
			
				|  |  | -            "[xds_client %p] LDS update received: route_config_name=%s",
 | 
	
		
			
				|  |  | -            xds_client(),
 | 
	
		
			
				|  |  | -            (!lds_update->route_config_name.empty()
 | 
	
		
			
				|  |  | -                 ? lds_update->route_config_name.c_str()
 | 
	
		
			
				|  |  | -                 : "<inlined>"));
 | 
	
		
			
				|  |  | -    if (lds_update->rds_update.has_value()) {
 | 
	
		
			
				|  |  | -      gpr_log(GPR_INFO, "RouteConfiguration: %s",
 | 
	
		
			
				|  |  | -              lds_update->rds_update->ToString().c_str());
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | +            "[xds_client %p] LDS update received containing %" PRIuPTR
 | 
	
		
			
				|  |  | +            " resources",
 | 
	
		
			
				|  |  | +            xds_client(), lds_update_map.size());
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  |    auto& lds_state = state_map_[XdsApi::kLdsTypeUrl];
 | 
	
		
			
				|  |  | -  auto& state = lds_state.subscribed_resources[xds_client()->server_name_];
 | 
	
		
			
				|  |  | -  if (state != nullptr) state->Finish();
 | 
	
		
			
				|  |  | -  // Ignore identical update.
 | 
	
		
			
				|  |  | -  if (xds_client()->lds_result_ == lds_update) {
 | 
	
		
			
				|  |  | +  std::set<std::string> rds_resource_names_seen;
 | 
	
		
			
				|  |  | +  for (auto& p : lds_update_map) {
 | 
	
		
			
				|  |  | +    const std::string& listener_name = p.first;
 | 
	
		
			
				|  |  | +    XdsApi::LdsUpdate& lds_update = p.second;
 | 
	
		
			
				|  |  | +    auto& state = lds_state.subscribed_resources[listener_name];
 | 
	
		
			
				|  |  | +    if (state != nullptr) state->Finish();
 | 
	
		
			
				|  |  |      if (GRPC_TRACE_FLAG_ENABLED(grpc_xds_client_trace)) {
 | 
	
		
			
				|  |  | -      gpr_log(GPR_INFO,
 | 
	
		
			
				|  |  | -              "[xds_client %p] LDS update identical to current, ignoring.",
 | 
	
		
			
				|  |  | -              xds_client());
 | 
	
		
			
				|  |  | +      gpr_log(GPR_INFO, "[xds_client %p] LDS resource %s: route_config_name=%s",
 | 
	
		
			
				|  |  | +              xds_client(), listener_name.c_str(),
 | 
	
		
			
				|  |  | +              (!lds_update.route_config_name.empty()
 | 
	
		
			
				|  |  | +                   ? lds_update.route_config_name.c_str()
 | 
	
		
			
				|  |  | +                   : "<inlined>"));
 | 
	
		
			
				|  |  | +      if (lds_update.rds_update.has_value()) {
 | 
	
		
			
				|  |  | +        gpr_log(GPR_INFO, "RouteConfiguration: %s",
 | 
	
		
			
				|  |  | +                lds_update.rds_update->ToString().c_str());
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +    // Record the RDS resource names seen.
 | 
	
		
			
				|  |  | +    if (!lds_update.route_config_name.empty()) {
 | 
	
		
			
				|  |  | +      rds_resource_names_seen.insert(lds_update.route_config_name);
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +    // Ignore identical update.
 | 
	
		
			
				|  |  | +    ListenerState& listener_state = xds_client()->listener_map_[listener_name];
 | 
	
		
			
				|  |  | +    if (listener_state.update.has_value() &&
 | 
	
		
			
				|  |  | +        *listener_state.update == lds_update) {
 | 
	
		
			
				|  |  | +      if (GRPC_TRACE_FLAG_ENABLED(grpc_xds_client_trace)) {
 | 
	
		
			
				|  |  | +        gpr_log(GPR_INFO,
 | 
	
		
			
				|  |  | +                "[xds_client %p] LDS update for %s identical to current, "
 | 
	
		
			
				|  |  | +                "ignoring.",
 | 
	
		
			
				|  |  | +                xds_client(), listener_name.c_str());
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  | +      continue;
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +    // Update the listener state.
 | 
	
		
			
				|  |  | +    listener_state.update = std::move(lds_update);
 | 
	
		
			
				|  |  | +    // Notify watchers.
 | 
	
		
			
				|  |  | +    for (const auto& p : listener_state.watchers) {
 | 
	
		
			
				|  |  | +      p.first->OnListenerChanged(*listener_state.update);
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  | -    return;
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  | -  if (xds_client()->lds_result_.has_value() &&
 | 
	
		
			
				|  |  | -      !xds_client()->lds_result_->route_config_name.empty()) {
 | 
	
		
			
				|  |  | -    Unsubscribe(
 | 
	
		
			
				|  |  | -        XdsApi::kRdsTypeUrl, xds_client()->lds_result_->route_config_name,
 | 
	
		
			
				|  |  | -        /*delay_unsubscription=*/!lds_update->route_config_name.empty());
 | 
	
		
			
				|  |  | -    xds_client()->rds_result_.reset();
 | 
	
		
			
				|  |  | -  }
 | 
	
		
			
				|  |  | -  xds_client()->lds_result_ = std::move(lds_update);
 | 
	
		
			
				|  |  | -  if (xds_client()->lds_result_->rds_update.has_value()) {
 | 
	
		
			
				|  |  | -    // If the RouteConfiguration was found inlined in LDS response, notify
 | 
	
		
			
				|  |  | -    // the watcher immediately.
 | 
	
		
			
				|  |  | -    const XdsApi::RdsUpdate::VirtualHost* vhost =
 | 
	
		
			
				|  |  | -        xds_client()->lds_result_->rds_update->FindVirtualHostForDomain(
 | 
	
		
			
				|  |  | -            xds_client()->server_name_);
 | 
	
		
			
				|  |  | -    if (vhost == nullptr) {
 | 
	
		
			
				|  |  | -      xds_client()->listener_watcher_->OnError(
 | 
	
		
			
				|  |  | -          GRPC_ERROR_CREATE_FROM_STATIC_STRING(
 | 
	
		
			
				|  |  | -              "no VirtualHost found for domain"));
 | 
	
		
			
				|  |  | -    } else {
 | 
	
		
			
				|  |  | -      xds_client()->listener_watcher_->OnListenerChanged(vhost->routes);
 | 
	
		
			
				|  |  | +  // For any subscribed resource that is not present in the update,
 | 
	
		
			
				|  |  | +  // remove it from the cache and notify watchers that it does not exist.
 | 
	
		
			
				|  |  | +  for (const auto& p : lds_state.subscribed_resources) {
 | 
	
		
			
				|  |  | +    const std::string& listener_name = p.first;
 | 
	
		
			
				|  |  | +    if (lds_update_map.find(listener_name) == lds_update_map.end()) {
 | 
	
		
			
				|  |  | +      ListenerState& listener_state =
 | 
	
		
			
				|  |  | +          xds_client()->listener_map_[listener_name];
 | 
	
		
			
				|  |  | +      // If the resource was newly requested but has not yet been received,
 | 
	
		
			
				|  |  | +      // we don't want to generate an error for the watchers, because this LDS
 | 
	
		
			
				|  |  | +      // response may be in reaction to an earlier request that did not yet
 | 
	
		
			
				|  |  | +      // request the new resource, so its absence from the response does not
 | 
	
		
			
				|  |  | +      // necessarily indicate that the resource does not exist.
 | 
	
		
			
				|  |  | +      // For that case, we rely on the request timeout instead.
 | 
	
		
			
				|  |  | +      if (!listener_state.update.has_value()) continue;
 | 
	
		
			
				|  |  | +      listener_state.update.reset();
 | 
	
		
			
				|  |  | +      for (const auto& p : listener_state.watchers) {
 | 
	
		
			
				|  |  | +        p.first->OnResourceDoesNotExist();
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  | +  // For any RDS resource that is no longer referred to by any LDS
 | 
	
		
			
				|  |  | +  // resources, remove it from the cache and notify watchers that it
 | 
	
		
			
				|  |  | +  // does not exist.
 | 
	
		
			
				|  |  | +  auto& rds_state = state_map_[XdsApi::kRdsTypeUrl];
 | 
	
		
			
				|  |  | +  for (const auto& p : rds_state.subscribed_resources) {
 | 
	
		
			
				|  |  | +    const std::string& rds_resource_name = p.first;
 | 
	
		
			
				|  |  | +    if (rds_resource_names_seen.find(rds_resource_name) ==
 | 
	
		
			
				|  |  | +        rds_resource_names_seen.end()) {
 | 
	
		
			
				|  |  | +      RouteConfigState& route_config_state =
 | 
	
		
			
				|  |  | +          xds_client()->route_config_map_[rds_resource_name];
 | 
	
		
			
				|  |  | +      route_config_state.update.reset();
 | 
	
		
			
				|  |  | +      for (const auto& p : route_config_state.watchers) {
 | 
	
		
			
				|  |  | +        p.first->OnResourceDoesNotExist();
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  | -  } else {
 | 
	
		
			
				|  |  | -    // Send RDS request for dynamic resolution.
 | 
	
		
			
				|  |  | -    Subscribe(XdsApi::kRdsTypeUrl,
 | 
	
		
			
				|  |  | -              xds_client()->lds_result_->route_config_name);
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  void XdsClient::ChannelState::AdsCallState::AcceptRdsUpdate(
 | 
	
		
			
				|  |  | -    absl::optional<XdsApi::RdsUpdate> rds_update) {
 | 
	
		
			
				|  |  | -  if (!rds_update.has_value()) {
 | 
	
		
			
				|  |  | -    gpr_log(GPR_INFO,
 | 
	
		
			
				|  |  | -            "[xds_client %p] RDS update does not include requested resource",
 | 
	
		
			
				|  |  | -            xds_client());
 | 
	
		
			
				|  |  | -    xds_client()->rds_result_.reset();
 | 
	
		
			
				|  |  | -    xds_client()->listener_watcher_->OnResourceDoesNotExist();
 | 
	
		
			
				|  |  | -    return;
 | 
	
		
			
				|  |  | -  }
 | 
	
		
			
				|  |  | +    XdsApi::RdsUpdateMap rds_update_map) {
 | 
	
		
			
				|  |  |    if (GRPC_TRACE_FLAG_ENABLED(grpc_xds_client_trace)) {
 | 
	
		
			
				|  |  | -    gpr_log(GPR_INFO, "[xds_client %p] RDS update received:\n%s", xds_client(),
 | 
	
		
			
				|  |  | -            rds_update->ToString().c_str());
 | 
	
		
			
				|  |  | +    gpr_log(GPR_INFO,
 | 
	
		
			
				|  |  | +            "[xds_client %p] RDS update received containing %" PRIuPTR
 | 
	
		
			
				|  |  | +            " resources",
 | 
	
		
			
				|  |  | +            xds_client(), rds_update_map.size());
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  | -  auto& rds_state = state_map_[XdsApi::kRdsTypeUrl];
 | 
	
		
			
				|  |  | -  auto& state =
 | 
	
		
			
				|  |  | -      rds_state
 | 
	
		
			
				|  |  | -          .subscribed_resources[xds_client()->lds_result_->route_config_name];
 | 
	
		
			
				|  |  | -  if (state != nullptr) state->Finish();
 | 
	
		
			
				|  |  | -  // Ignore identical update.
 | 
	
		
			
				|  |  | -  if (xds_client()->rds_result_ == rds_update) {
 | 
	
		
			
				|  |  | +  auto& rds_state = state_map_[XdsApi::kLdsTypeUrl];
 | 
	
		
			
				|  |  | +  for (auto& p : rds_update_map) {
 | 
	
		
			
				|  |  | +    const std::string& route_config_name = p.first;
 | 
	
		
			
				|  |  | +    XdsApi::RdsUpdate& rds_update = p.second;
 | 
	
		
			
				|  |  | +    auto& state = rds_state.subscribed_resources[route_config_name];
 | 
	
		
			
				|  |  | +    if (state != nullptr) state->Finish();
 | 
	
		
			
				|  |  |      if (GRPC_TRACE_FLAG_ENABLED(grpc_xds_client_trace)) {
 | 
	
		
			
				|  |  | -      gpr_log(GPR_INFO,
 | 
	
		
			
				|  |  | -              "[xds_client %p] RDS update identical to current, ignoring.",
 | 
	
		
			
				|  |  | -              xds_client());
 | 
	
		
			
				|  |  | +      gpr_log(GPR_INFO, "[xds_client %p] RDS resource:\n%s", xds_client(),
 | 
	
		
			
				|  |  | +              rds_update.ToString().c_str());
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +    RouteConfigState& route_config_state =
 | 
	
		
			
				|  |  | +        xds_client()->route_config_map_[route_config_name];
 | 
	
		
			
				|  |  | +    // Ignore identical update.
 | 
	
		
			
				|  |  | +    if (route_config_state.update.has_value() &&
 | 
	
		
			
				|  |  | +        *route_config_state.update == rds_update) {
 | 
	
		
			
				|  |  | +      if (GRPC_TRACE_FLAG_ENABLED(grpc_xds_client_trace)) {
 | 
	
		
			
				|  |  | +        gpr_log(GPR_INFO,
 | 
	
		
			
				|  |  | +                "[xds_client %p] RDS resource identical to current, ignoring",
 | 
	
		
			
				|  |  | +                xds_client());
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  | +      continue;
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +    // Update the cache.
 | 
	
		
			
				|  |  | +    route_config_state.update = std::move(rds_update);
 | 
	
		
			
				|  |  | +    // Notify all watchers.
 | 
	
		
			
				|  |  | +    for (const auto& p : route_config_state.watchers) {
 | 
	
		
			
				|  |  | +      p.first->OnRouteConfigChanged(*route_config_state.update);
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  | -    return;
 | 
	
		
			
				|  |  | -  }
 | 
	
		
			
				|  |  | -  xds_client()->rds_result_ = std::move(rds_update);
 | 
	
		
			
				|  |  | -  // Notify the watcher.
 | 
	
		
			
				|  |  | -  const XdsApi::RdsUpdate::VirtualHost* vhost =
 | 
	
		
			
				|  |  | -      xds_client()->rds_result_->FindVirtualHostForDomain(
 | 
	
		
			
				|  |  | -          xds_client()->server_name_);
 | 
	
		
			
				|  |  | -  if (vhost == nullptr) {
 | 
	
		
			
				|  |  | -    xds_client()->listener_watcher_->OnError(
 | 
	
		
			
				|  |  | -        GRPC_ERROR_CREATE_FROM_STATIC_STRING(
 | 
	
		
			
				|  |  | -            "no VirtualHost found for domain"));
 | 
	
		
			
				|  |  | -  } else {
 | 
	
		
			
				|  |  | -    xds_client()->listener_watcher_->OnListenerChanged(vhost->routes);
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -1008,9 +1031,7 @@ void XdsClient::ChannelState::AdsCallState::AcceptCdsUpdate(
 | 
	
		
			
				|  |  |      // Ignore identical update.
 | 
	
		
			
				|  |  |      ClusterState& cluster_state = xds_client()->cluster_map_[cluster_name];
 | 
	
		
			
				|  |  |      if (cluster_state.update.has_value() &&
 | 
	
		
			
				|  |  | -        cds_update.eds_service_name == cluster_state.update->eds_service_name &&
 | 
	
		
			
				|  |  | -        cds_update.lrs_load_reporting_server_name ==
 | 
	
		
			
				|  |  | -            cluster_state.update->lrs_load_reporting_server_name) {
 | 
	
		
			
				|  |  | +        *cluster_state.update == cds_update) {
 | 
	
		
			
				|  |  |        if (GRPC_TRACE_FLAG_ENABLED(grpc_xds_client_trace)) {
 | 
	
		
			
				|  |  |          gpr_log(GPR_INFO,
 | 
	
		
			
				|  |  |                  "[xds_client %p] CDS update identical to current, ignoring.",
 | 
	
	
		
			
				|  | @@ -1157,7 +1178,7 @@ void XdsClient::ChannelState::AdsCallState::OnResponseReceivedLocked() {
 | 
	
		
			
				|  |  |    recv_message_payload_ = nullptr;
 | 
	
		
			
				|  |  |    // Parse and validate the response.
 | 
	
		
			
				|  |  |    XdsApi::AdsParseResult result = xds_client()->api_.ParseAdsResponse(
 | 
	
		
			
				|  |  | -      response_slice, xds_client()->server_name_,
 | 
	
		
			
				|  |  | +      response_slice, ResourceNamesForRequest(XdsApi::kLdsTypeUrl),
 | 
	
		
			
				|  |  |        ResourceNamesForRequest(XdsApi::kRdsTypeUrl),
 | 
	
		
			
				|  |  |        ResourceNamesForRequest(XdsApi::kCdsTypeUrl),
 | 
	
		
			
				|  |  |        ResourceNamesForRequest(XdsApi::kEdsTypeUrl));
 | 
	
	
		
			
				|  | @@ -1187,9 +1208,9 @@ void XdsClient::ChannelState::AdsCallState::OnResponseReceivedLocked() {
 | 
	
		
			
				|  |  |        seen_response_ = true;
 | 
	
		
			
				|  |  |        // Accept the ADS response according to the type_url.
 | 
	
		
			
				|  |  |        if (result.type_url == XdsApi::kLdsTypeUrl) {
 | 
	
		
			
				|  |  | -        AcceptLdsUpdate(std::move(result.lds_update));
 | 
	
		
			
				|  |  | +        AcceptLdsUpdate(std::move(result.lds_update_map));
 | 
	
		
			
				|  |  |        } else if (result.type_url == XdsApi::kRdsTypeUrl) {
 | 
	
		
			
				|  |  | -        AcceptRdsUpdate(std::move(result.rds_update));
 | 
	
		
			
				|  |  | +        AcceptRdsUpdate(std::move(result.rds_update_map));
 | 
	
		
			
				|  |  |        } else if (result.type_url == XdsApi::kCdsTypeUrl) {
 | 
	
		
			
				|  |  |          AcceptCdsUpdate(std::move(result.cds_update_map));
 | 
	
		
			
				|  |  |        } else if (result.type_url == XdsApi::kEdsTypeUrl) {
 | 
	
	
		
			
				|  | @@ -1272,7 +1293,7 @@ XdsClient::ChannelState::AdsCallState::ResourceNamesForRequest(
 | 
	
		
			
				|  |  |      for (auto& p : it->second.subscribed_resources) {
 | 
	
		
			
				|  |  |        resource_names.insert(p.first);
 | 
	
		
			
				|  |  |        OrphanablePtr<ResourceState>& state = p.second;
 | 
	
		
			
				|  |  | -      state->Start(Ref());
 | 
	
		
			
				|  |  | +      state->Start(Ref(DEBUG_LOCATION, "ResourceState"));
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  |    return resource_names;
 | 
	
	
		
			
				|  | @@ -1746,19 +1767,16 @@ grpc_channel* CreateXdsChannel(const XdsBootstrap& bootstrap,
 | 
	
		
			
				|  |  |  }  // namespace
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  XdsClient::XdsClient(std::shared_ptr<WorkSerializer> work_serializer,
 | 
	
		
			
				|  |  | -                     grpc_pollset_set* interested_parties,
 | 
	
		
			
				|  |  |                       absl::string_view server_name,
 | 
	
		
			
				|  |  | -                     std::unique_ptr<ListenerWatcherInterface> watcher,
 | 
	
		
			
				|  |  |                       const grpc_channel_args& channel_args, grpc_error** error)
 | 
	
		
			
				|  |  |      : InternallyRefCounted<XdsClient>(&grpc_xds_client_trace),
 | 
	
		
			
				|  |  |        request_timeout_(GetRequestTimeout(channel_args)),
 | 
	
		
			
				|  |  |        work_serializer_(std::move(work_serializer)),
 | 
	
		
			
				|  |  | -      interested_parties_(interested_parties),
 | 
	
		
			
				|  |  | +      interested_parties_(grpc_pollset_set_create()),
 | 
	
		
			
				|  |  |        bootstrap_(
 | 
	
		
			
				|  |  |            XdsBootstrap::ReadFromFile(this, &grpc_xds_client_trace, error)),
 | 
	
		
			
				|  |  |        api_(this, &grpc_xds_client_trace, bootstrap_.get()),
 | 
	
		
			
				|  |  | -      server_name_(server_name),
 | 
	
		
			
				|  |  | -      listener_watcher_(std::move(watcher)) {
 | 
	
		
			
				|  |  | +      server_name_(server_name) {
 | 
	
		
			
				|  |  |    if (GRPC_TRACE_FLAG_ENABLED(grpc_xds_client_trace)) {
 | 
	
		
			
				|  |  |      gpr_log(GPR_INFO, "[xds_client %p] creating xds client", this);
 | 
	
		
			
				|  |  |    }
 | 
	
	
		
			
				|  | @@ -1781,15 +1799,13 @@ XdsClient::XdsClient(std::shared_ptr<WorkSerializer> work_serializer,
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  |    chand_ = MakeOrphanable<ChannelState>(
 | 
	
		
			
				|  |  |        Ref(DEBUG_LOCATION, "XdsClient+ChannelState"), channel);
 | 
	
		
			
				|  |  | -  if (listener_watcher_ != nullptr) {
 | 
	
		
			
				|  |  | -    chand_->Subscribe(XdsApi::kLdsTypeUrl, std::string(server_name));
 | 
	
		
			
				|  |  | -  }
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  XdsClient::~XdsClient() {
 | 
	
		
			
				|  |  |    if (GRPC_TRACE_FLAG_ENABLED(grpc_xds_client_trace)) {
 | 
	
		
			
				|  |  |      gpr_log(GPR_INFO, "[xds_client %p] destroying xds client", this);
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  | +  grpc_pollset_set_destroy(interested_parties_);
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  void XdsClient::Orphan() {
 | 
	
	
		
			
				|  | @@ -1804,13 +1820,88 @@ void XdsClient::Orphan() {
 | 
	
		
			
				|  |  |    // possible for ADS calls to be in progress. Unreffing the loadbalancing
 | 
	
		
			
				|  |  |    // policies before those calls are done would lead to issues such as
 | 
	
		
			
				|  |  |    // https://github.com/grpc/grpc/issues/20928.
 | 
	
		
			
				|  |  | -  if (listener_watcher_ != nullptr) {
 | 
	
		
			
				|  |  | +  if (!listener_map_.empty()) {
 | 
	
		
			
				|  |  |      cluster_map_.clear();
 | 
	
		
			
				|  |  |      endpoint_map_.clear();
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  |    Unref(DEBUG_LOCATION, "XdsClient::Orphan()");
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +void XdsClient::WatchListenerData(
 | 
	
		
			
				|  |  | +    absl::string_view listener_name,
 | 
	
		
			
				|  |  | +    std::unique_ptr<ListenerWatcherInterface> watcher) {
 | 
	
		
			
				|  |  | +  std::string listener_name_str = std::string(listener_name);
 | 
	
		
			
				|  |  | +  ListenerState& listener_state = listener_map_[listener_name_str];
 | 
	
		
			
				|  |  | +  ListenerWatcherInterface* w = watcher.get();
 | 
	
		
			
				|  |  | +  listener_state.watchers[w] = std::move(watcher);
 | 
	
		
			
				|  |  | +  // If we've already received an LDS update, notify the new watcher
 | 
	
		
			
				|  |  | +  // immediately.
 | 
	
		
			
				|  |  | +  if (listener_state.update.has_value()) {
 | 
	
		
			
				|  |  | +    if (GRPC_TRACE_FLAG_ENABLED(grpc_xds_client_trace)) {
 | 
	
		
			
				|  |  | +      gpr_log(GPR_INFO, "[xds_client %p] returning cached listener data for %s",
 | 
	
		
			
				|  |  | +              this, listener_name_str.c_str());
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +    w->OnListenerChanged(*listener_state.update);
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  | +  chand_->Subscribe(XdsApi::kLdsTypeUrl, listener_name_str);
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +void XdsClient::CancelListenerDataWatch(absl::string_view listener_name,
 | 
	
		
			
				|  |  | +                                        ListenerWatcherInterface* watcher,
 | 
	
		
			
				|  |  | +                                        bool delay_unsubscription) {
 | 
	
		
			
				|  |  | +  if (shutting_down_) return;
 | 
	
		
			
				|  |  | +  std::string listener_name_str = std::string(listener_name);
 | 
	
		
			
				|  |  | +  ListenerState& listener_state = listener_map_[listener_name_str];
 | 
	
		
			
				|  |  | +  auto it = listener_state.watchers.find(watcher);
 | 
	
		
			
				|  |  | +  if (it != listener_state.watchers.end()) {
 | 
	
		
			
				|  |  | +    listener_state.watchers.erase(it);
 | 
	
		
			
				|  |  | +    if (listener_state.watchers.empty()) {
 | 
	
		
			
				|  |  | +      listener_map_.erase(listener_name_str);
 | 
	
		
			
				|  |  | +      chand_->Unsubscribe(XdsApi::kLdsTypeUrl, listener_name_str,
 | 
	
		
			
				|  |  | +                          delay_unsubscription);
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +void XdsClient::WatchRouteConfigData(
 | 
	
		
			
				|  |  | +    absl::string_view route_config_name,
 | 
	
		
			
				|  |  | +    std::unique_ptr<RouteConfigWatcherInterface> watcher) {
 | 
	
		
			
				|  |  | +  std::string route_config_name_str = std::string(route_config_name);
 | 
	
		
			
				|  |  | +  RouteConfigState& route_config_state =
 | 
	
		
			
				|  |  | +      route_config_map_[route_config_name_str];
 | 
	
		
			
				|  |  | +  RouteConfigWatcherInterface* w = watcher.get();
 | 
	
		
			
				|  |  | +  route_config_state.watchers[w] = std::move(watcher);
 | 
	
		
			
				|  |  | +  // If we've already received an RDS update, notify the new watcher
 | 
	
		
			
				|  |  | +  // immediately.
 | 
	
		
			
				|  |  | +  if (route_config_state.update.has_value()) {
 | 
	
		
			
				|  |  | +    if (GRPC_TRACE_FLAG_ENABLED(grpc_xds_client_trace)) {
 | 
	
		
			
				|  |  | +      gpr_log(GPR_INFO,
 | 
	
		
			
				|  |  | +              "[xds_client %p] returning cached route config data for %s", this,
 | 
	
		
			
				|  |  | +              route_config_name_str.c_str());
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +    w->OnRouteConfigChanged(*route_config_state.update);
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  | +  chand_->Subscribe(XdsApi::kRdsTypeUrl, route_config_name_str);
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +void XdsClient::CancelRouteConfigDataWatch(absl::string_view route_config_name,
 | 
	
		
			
				|  |  | +                                           RouteConfigWatcherInterface* watcher,
 | 
	
		
			
				|  |  | +                                           bool delay_unsubscription) {
 | 
	
		
			
				|  |  | +  if (shutting_down_) return;
 | 
	
		
			
				|  |  | +  std::string route_config_name_str = std::string(route_config_name);
 | 
	
		
			
				|  |  | +  RouteConfigState& route_config_state =
 | 
	
		
			
				|  |  | +      route_config_map_[route_config_name_str];
 | 
	
		
			
				|  |  | +  auto it = route_config_state.watchers.find(watcher);
 | 
	
		
			
				|  |  | +  if (it != route_config_state.watchers.end()) {
 | 
	
		
			
				|  |  | +    route_config_state.watchers.erase(it);
 | 
	
		
			
				|  |  | +    if (route_config_state.watchers.empty()) {
 | 
	
		
			
				|  |  | +      route_config_map_.erase(route_config_name_str);
 | 
	
		
			
				|  |  | +      chand_->Unsubscribe(XdsApi::kRdsTypeUrl, route_config_name_str,
 | 
	
		
			
				|  |  | +                          delay_unsubscription);
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  void XdsClient::WatchClusterData(
 | 
	
		
			
				|  |  |      absl::string_view cluster_name,
 | 
	
		
			
				|  |  |      std::unique_ptr<ClusterWatcherInterface> watcher) {
 | 
	
	
		
			
				|  | @@ -1818,7 +1909,7 @@ void XdsClient::WatchClusterData(
 | 
	
		
			
				|  |  |    ClusterState& cluster_state = cluster_map_[cluster_name_str];
 | 
	
		
			
				|  |  |    ClusterWatcherInterface* w = watcher.get();
 | 
	
		
			
				|  |  |    cluster_state.watchers[w] = std::move(watcher);
 | 
	
		
			
				|  |  | -  // If we've already received an CDS update, notify the new watcher
 | 
	
		
			
				|  |  | +  // If we've already received a CDS update, notify the new watcher
 | 
	
		
			
				|  |  |    // immediately.
 | 
	
		
			
				|  |  |    if (cluster_state.update.has_value()) {
 | 
	
		
			
				|  |  |      if (GRPC_TRACE_FLAG_ENABLED(grpc_xds_client_trace)) {
 | 
	
	
		
			
				|  | @@ -2048,8 +2139,17 @@ XdsApi::ClusterLoadReportMap XdsClient::BuildLoadReportSnapshot(
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  void XdsClient::NotifyOnError(grpc_error* error) {
 | 
	
		
			
				|  |  | -  if (listener_watcher_ != nullptr) {
 | 
	
		
			
				|  |  | -    listener_watcher_->OnError(GRPC_ERROR_REF(error));
 | 
	
		
			
				|  |  | +  for (const auto& p : listener_map_) {
 | 
	
		
			
				|  |  | +    const ListenerState& listener_state = p.second;
 | 
	
		
			
				|  |  | +    for (const auto& p : listener_state.watchers) {
 | 
	
		
			
				|  |  | +      p.first->OnError(GRPC_ERROR_REF(error));
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  | +  for (const auto& p : route_config_map_) {
 | 
	
		
			
				|  |  | +    const RouteConfigState& route_config_state = p.second;
 | 
	
		
			
				|  |  | +    for (const auto& p : route_config_state.watchers) {
 | 
	
		
			
				|  |  | +      p.first->OnError(GRPC_ERROR_REF(error));
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  |    for (const auto& p : cluster_map_) {
 | 
	
		
			
				|  |  |      const ClusterState& cluster_state = p.second;
 | 
	
	
		
			
				|  | @@ -2093,8 +2193,8 @@ RefCountedPtr<XdsClient> XdsClient::GetFromChannelArgs(
 | 
	
		
			
				|  |  |      const grpc_channel_args& args) {
 | 
	
		
			
				|  |  |    XdsClient* xds_client =
 | 
	
		
			
				|  |  |        grpc_channel_args_find_pointer<XdsClient>(&args, GRPC_ARG_XDS_CLIENT);
 | 
	
		
			
				|  |  | -  if (xds_client != nullptr) return xds_client->Ref();
 | 
	
		
			
				|  |  | -  return nullptr;
 | 
	
		
			
				|  |  | +  if (xds_client == nullptr) return nullptr;
 | 
	
		
			
				|  |  | +  return xds_client->Ref(DEBUG_LOCATION, "GetFromChannelArgs");
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  grpc_channel_args* XdsClient::RemoveFromChannelArgs(
 |