From 142e378cea0aa90aae36fec55f90c30ede95f965 Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Fri, 30 May 2025 09:00:27 -0700 Subject: [PATCH] xds: Improve shutdown handling of XdsDepManager The most important change here is to handle subscribeToCluster() calls after shutdown(), and preventing the internal state from being heavily confused as the assumption is there are no watchers after shutdown(). ClusterSubscription.closed isn't strictly necessary, but I don't want the code to depend on double-deregistration being safe. maybePublishConfig() isn't being called after shutdown(), but adding the protection avoids a class of bugs that would cause channel panic. --- .../io/grpc/xds/XdsDependencyManager.java | 29 ++++++++++++++----- .../io/grpc/xds/XdsDependencyManagerTest.java | 16 ++++++++++ 2 files changed, 38 insertions(+), 7 deletions(-) diff --git a/xds/src/main/java/io/grpc/xds/XdsDependencyManager.java b/xds/src/main/java/io/grpc/xds/XdsDependencyManager.java index d804954ecf..1ba4963186 100644 --- a/xds/src/main/java/io/grpc/xds/XdsDependencyManager.java +++ b/xds/src/main/java/io/grpc/xds/XdsDependencyManager.java @@ -93,13 +93,15 @@ final class XdsDependencyManager implements XdsConfig.XdsClusterSubscriptionRegi @Override public Closeable subscribeToCluster(String clusterName) { - checkNotNull(clusterName, "clusterName"); ClusterSubscription subscription = new ClusterSubscription(clusterName); syncContext.execute(() -> { + if (getWatchers(XdsListenerResource.getInstance()).isEmpty()) { + subscription.closed = true; + return; // shutdown() called + } addClusterWatcher(clusterName, subscription, 1); - maybePublishConfig(); }); return subscription; @@ -207,10 +209,14 @@ final class XdsDependencyManager implements XdsConfig.XdsClusterSubscriptionRegi checkNotNull(subscription, "subscription"); String clusterName = subscription.getClusterName(); syncContext.execute(() -> { + if (subscription.closed) { + return; + } + subscription.closed = true; XdsWatcherBase cdsWatcher = resourceWatchers.get(CLUSTER_RESOURCE).watchers.get(clusterName); if (cdsWatcher == null) { - return; // already released while waiting for the syncContext + return; // shutdown() called } cancelClusterWatcherTree((CdsWatcher) cdsWatcher, subscription); maybePublishConfig(); @@ -257,6 +263,9 @@ final class XdsDependencyManager implements XdsConfig.XdsClusterSubscriptionRegi */ private void maybePublishConfig() { syncContext.throwIfNotInThisSynchronizationContext(); + if (getWatchers(XdsListenerResource.getInstance()).isEmpty()) { + return; // shutdown() called + } boolean waitingOnResource = resourceWatchers.values().stream() .flatMap(typeWatchers -> typeWatchers.watchers.values().stream()) .anyMatch(XdsWatcherBase::missingResult); @@ -293,6 +302,11 @@ final class XdsDependencyManager implements XdsConfig.XdsClusterSubscriptionRegi routeSource = ((LdsWatcher) ldsWatcher).getRouteSource(); } + if (routeSource == null) { + return StatusOr.fromStatus(Status.UNAVAILABLE.withDescription( + "Bug: No route source found for listener " + dataPlaneAuthority)); + } + StatusOr statusOrRdsUpdate = routeSource.getRdsUpdate(); if (!statusOrRdsUpdate.hasValue()) { return StatusOr.fromStatus(statusOrRdsUpdate.getStatus()); @@ -557,14 +571,15 @@ final class XdsDependencyManager implements XdsConfig.XdsClusterSubscriptionRegi void onUpdate(StatusOr config); } - private class ClusterSubscription implements Closeable { - String clusterName; + private final class ClusterSubscription implements Closeable { + private final String clusterName; + boolean closed; // Accessed from syncContext public ClusterSubscription(String clusterName) { - this.clusterName = clusterName; + this.clusterName = checkNotNull(clusterName, "clusterName"); } - public String getClusterName() { + String getClusterName() { return clusterName; } diff --git a/xds/src/test/java/io/grpc/xds/XdsDependencyManagerTest.java b/xds/src/test/java/io/grpc/xds/XdsDependencyManagerTest.java index 1f3d8511ec..8a24d21f77 100644 --- a/xds/src/test/java/io/grpc/xds/XdsDependencyManagerTest.java +++ b/xds/src/test/java/io/grpc/xds/XdsDependencyManagerTest.java @@ -848,6 +848,22 @@ public class XdsDependencyManagerTest { }); } + @Test + public void subscribeToClusterAfterShutdown() throws Exception { + XdsTestUtils.setAdsConfig(controlPlaneService, serverName, "RDS", "CDS", "EDS", + ENDPOINT_HOSTNAME, ENDPOINT_PORT); + + InOrder inOrder = Mockito.inOrder(xdsConfigWatcher); + xdsDependencyManager = new XdsDependencyManager(xdsClient, xdsConfigWatcher, syncContext, + serverName, serverName, nameResolverArgs, scheduler); + inOrder.verify(xdsConfigWatcher).onUpdate(any()); + xdsDependencyManager.shutdown(); + + Closeable subscription = xdsDependencyManager.subscribeToCluster("CDS"); + inOrder.verify(xdsConfigWatcher, never()).onUpdate(any()); + subscription.close(); + } + private Listener buildInlineClientListener(String rdsName, String clusterName) { return XdsTestUtils.buildInlineClientListener(rdsName, clusterName, serverName); }