This is an automated email from the ASF dual-hosted git repository.

shunping pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/beam.git


The following commit(s) were added to refs/heads/master by this push:
     new 0072521920c [Java] Fix GRPC channel leak (#39663)
0072521920c is described below

commit 0072521920c2d4219f3191e2ffd748ee4b84babb
Author: Shunping Huang <[email protected]>
AuthorDate: Fri Oct 16 08:41:32 2026 -0400

    [Java] Fix GRPC channel leak (#39663)
---
 .../beam/fn/harness/ExternalWorkerService.java     | 33 +++++++++++++---------
 .../java/org/apache/beam/fn/harness/FnHarness.java |  1 +
 .../fn/harness/control/ProcessBundleHandler.java   |  3 ++
 .../beam/fn/harness/data/BeamFnDataGrpcClient.java | 11 ++++++--
 .../harness/state/BeamFnStateGrpcClientCache.java  | 20 +++++++++++--
 5 files changed, 50 insertions(+), 18 deletions(-)

diff --git 
a/sdks/java/harness/src/main/java/org/apache/beam/fn/harness/ExternalWorkerService.java
 
b/sdks/java/harness/src/main/java/org/apache/beam/fn/harness/ExternalWorkerService.java
index 466d8ef3193..b1bc462702d 100644
--- 
a/sdks/java/harness/src/main/java/org/apache/beam/fn/harness/ExternalWorkerService.java
+++ 
b/sdks/java/harness/src/main/java/org/apache/beam/fn/harness/ExternalWorkerService.java
@@ -39,6 +39,7 @@ import org.apache.beam.sdk.options.PortablePipelineOptions;
 import org.apache.beam.sdk.util.Sleeper;
 import org.apache.beam.sdk.util.construction.Environments;
 import org.apache.beam.sdk.util.construction.PipelineOptionsTranslation;
+import org.apache.beam.vendor.grpc.v1p69p0.io.grpc.ManagedChannel;
 import org.apache.beam.vendor.grpc.v1p69p0.io.grpc.stub.StreamObserver;
 import 
org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.collect.ImmutableList;
 import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.collect.Sets;
@@ -78,20 +79,24 @@ public class ExternalWorkerService extends 
BeamFnExternalWorkerPoolImplBase impl
               .withInterceptors(
                   
ImmutableList.of(AddHarnessIdInterceptor.create(request.getWorkerId())));
 
-      ProvisionServiceGrpc.ProvisionServiceBlockingStub provisionStub =
-          ProvisionServiceGrpc.newBlockingStub(
-              channelFactory.forDescriptor(request.getProvisionEndpoint()));
-      ProvisionApi.ProvisionInfo provisionInfo =
-          provisionStub
-              
.getProvisionInfo(ProvisionApi.GetProvisionInfoRequest.newBuilder().build())
-              .getInfo();
-
-      runnerCapabilites = 
Sets.newHashSet(provisionInfo.getRunnerCapabilitiesList());
-      if (provisionInfo.hasControlEndpoint()) {
-        controlEndpoint = provisionInfo.getControlEndpoint();
-      }
-      if (provisionInfo.hasLoggingEndpoint()) {
-        loggingEndpoint = provisionInfo.getLoggingEndpoint();
+      ManagedChannel channel = 
channelFactory.forDescriptor(request.getProvisionEndpoint());
+      try {
+        ProvisionServiceGrpc.ProvisionServiceBlockingStub provisionStub =
+            ProvisionServiceGrpc.newBlockingStub(channel);
+        ProvisionApi.ProvisionInfo provisionInfo =
+            provisionStub
+                
.getProvisionInfo(ProvisionApi.GetProvisionInfoRequest.newBuilder().build())
+                .getInfo();
+
+        runnerCapabilites = 
Sets.newHashSet(provisionInfo.getRunnerCapabilitiesList());
+        if (provisionInfo.hasControlEndpoint()) {
+          controlEndpoint = provisionInfo.getControlEndpoint();
+        }
+        if (provisionInfo.hasLoggingEndpoint()) {
+          loggingEndpoint = provisionInfo.getLoggingEndpoint();
+        }
+      } finally {
+        channel.shutdown();
       }
     }
     // Lambda closured variables must be final.
diff --git 
a/sdks/java/harness/src/main/java/org/apache/beam/fn/harness/FnHarness.java 
b/sdks/java/harness/src/main/java/org/apache/beam/fn/harness/FnHarness.java
index 63f6c41b5a7..f547a1eeadf 100644
--- a/sdks/java/harness/src/main/java/org/apache/beam/fn/harness/FnHarness.java
+++ b/sdks/java/harness/src/main/java/org/apache/beam/fn/harness/FnHarness.java
@@ -437,6 +437,7 @@ public class FnHarness {
         beamFnStatusClient.close();
       }
       processBundleHandler.shutdown();
+      channel.shutdown();
     } catch (Exception e) {
       LOG.error("Shutting down harness due to exception", e);
       e.printStackTrace();
diff --git 
a/sdks/java/harness/src/main/java/org/apache/beam/fn/harness/control/ProcessBundleHandler.java
 
b/sdks/java/harness/src/main/java/org/apache/beam/fn/harness/control/ProcessBundleHandler.java
index f12340552fd..9fd1f0f0d68 100644
--- 
a/sdks/java/harness/src/main/java/org/apache/beam/fn/harness/control/ProcessBundleHandler.java
+++ 
b/sdks/java/harness/src/main/java/org/apache/beam/fn/harness/control/ProcessBundleHandler.java
@@ -768,6 +768,9 @@ public class ProcessBundleHandler {
   public void shutdown() throws Exception {
     bundleProcessorCache.shutdown();
     beamFnDataClient.close();
+    if (beamFnStateGrpcClientCache != null) {
+      beamFnStateGrpcClientCache.close();
+    }
   }
 
   @VisibleForTesting
diff --git 
a/sdks/java/harness/src/main/java/org/apache/beam/fn/harness/data/BeamFnDataGrpcClient.java
 
b/sdks/java/harness/src/main/java/org/apache/beam/fn/harness/data/BeamFnDataGrpcClient.java
index 79e34fe5765..0640cc0d289 100644
--- 
a/sdks/java/harness/src/main/java/org/apache/beam/fn/harness/data/BeamFnDataGrpcClient.java
+++ 
b/sdks/java/harness/src/main/java/org/apache/beam/fn/harness/data/BeamFnDataGrpcClient.java
@@ -76,6 +76,7 @@ public class BeamFnDataGrpcClient implements BeamFnDataClient 
{
   }
 
   private final ConcurrentMap<MultiplexerKey, BeamFnDataGrpcMultiplexer> 
multiplexerCache;
+  private final ConcurrentMap<MultiplexerKey, ManagedChannel> channelCache;
   private final Function<Endpoints.ApiServiceDescriptor, ManagedChannel> 
channelFactory;
   private final OutboundObserverFactory outboundObserverFactory;
 
@@ -85,6 +86,7 @@ public class BeamFnDataGrpcClient implements BeamFnDataClient 
{
     this.channelFactory = channelFactory;
     this.outboundObserverFactory = outboundObserverFactory;
     this.multiplexerCache = new ConcurrentHashMap<>();
+    this.channelCache = new ConcurrentHashMap<>();
   }
 
   @Override
@@ -128,6 +130,10 @@ public class BeamFnDataGrpcClient implements 
BeamFnDataClient {
       }
     }
     multiplexerCache.clear();
+    for (ManagedChannel channel : channelCache.values()) {
+      channel.shutdown();
+    }
+    channelCache.clear();
   }
 
   @Override
@@ -142,10 +148,11 @@ public class BeamFnDataGrpcClient implements 
BeamFnDataClient {
     return multiplexerCache.computeIfAbsent(
         key,
         k -> {
+          ManagedChannel channel =
+              channelCache.computeIfAbsent(k, unused -> 
channelFactory.apply(apiServiceDescriptor));
           OutboundObserverFactory.BasicFactory<Elements, Elements> 
baseOutboundObserverFactory =
               inboundObserver -> {
-                BeamFnDataGrpc.BeamFnDataStub stub =
-                    
BeamFnDataGrpc.newStub(channelFactory.apply(apiServiceDescriptor));
+                BeamFnDataGrpc.BeamFnDataStub stub = 
BeamFnDataGrpc.newStub(channel);
                 if (dataStreamId != null && !dataStreamId.isEmpty()) {
                   Metadata headers = new Metadata();
                   headers.put(
diff --git 
a/sdks/java/harness/src/main/java/org/apache/beam/fn/harness/state/BeamFnStateGrpcClientCache.java
 
b/sdks/java/harness/src/main/java/org/apache/beam/fn/harness/state/BeamFnStateGrpcClientCache.java
index c9adb9d7d14..1a0c4f3fb91 100644
--- 
a/sdks/java/harness/src/main/java/org/apache/beam/fn/harness/state/BeamFnStateGrpcClientCache.java
+++ 
b/sdks/java/harness/src/main/java/org/apache/beam/fn/harness/state/BeamFnStateGrpcClientCache.java
@@ -18,7 +18,9 @@
 package org.apache.beam.fn.harness.state;
 
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.concurrent.CompletableFuture;
 import org.apache.beam.model.fnexecution.v1.BeamFnApi.StateRequest;
@@ -44,7 +46,7 @@ import org.slf4j.LoggerFactory;
 public class BeamFnStateGrpcClientCache {
   private static final Logger LOG = 
LoggerFactory.getLogger(BeamFnStateGrpcClientCache.class);
 
-  private final Map<ApiServiceDescriptor, BeamFnStateClient> cache;
+  private final Map<ApiServiceDescriptor, GrpcStateClient> cache;
   private final ManagedChannelFactory channelFactory;
   private final OutboundObserverFactory outboundObserverFactory;
   private final IdGenerator idGenerator;
@@ -71,7 +73,7 @@ public class BeamFnStateGrpcClientCache {
     // We specifically are synchronized so that we only create one 
GrpcStateClient at a time
     // preventing a race where multiple GrpcStateClient objects might be 
constructed at the same
     // for the same ApiServiceDescriptor.
-    BeamFnStateClient rval;
+    GrpcStateClient rval;
     synchronized (cache) {
       rval = cache.get(apiServiceDescriptor);
     }
@@ -87,6 +89,18 @@ public class BeamFnStateGrpcClientCache {
     return rval;
   }
 
+  /** Shuts down the underlying channels of all cached state clients. */
+  public void close() {
+    List<GrpcStateClient> clients;
+    synchronized (cache) {
+      clients = new ArrayList<>(cache.values());
+      cache.clear();
+    }
+    for (GrpcStateClient client : clients) {
+      client.channel.shutdown();
+    }
+  }
+
   /** A {@link BeamFnStateClient} for a given {@link ApiServiceDescriptor}. */
   private class GrpcStateClient implements BeamFnStateClient {
     private final Object lock = new Object();
@@ -111,6 +125,7 @@ public class BeamFnStateGrpcClientCache {
       synchronized (lock) {
         if (errorDuringConstruction) {
           outboundObserver.onCompleted();
+          channel.shutdown();
         }
       }
     }
@@ -160,6 +175,7 @@ public class BeamFnStateGrpcClientCache {
           errorDuringConstruction = true;
         } else {
           outboundObserver.onCompleted();
+          channel.shutdown();
         }
       }
     }

Reply via email to