Move BuildEventServiceClient lifetime management in to the BuildEventServiceModule implementations, allowing them to cache connections to a backend across blaze invocations.

PiperOrigin-RevId: 227747738
diff --git a/src/main/java/com/google/devtools/build/lib/buildeventservice/BUILD b/src/main/java/com/google/devtools/build/lib/buildeventservice/BUILD
index 5f3dad0..5b00d29 100644
--- a/src/main/java/com/google/devtools/build/lib/buildeventservice/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/buildeventservice/BUILD
@@ -36,6 +36,7 @@
         "//src/main/java/com/google/devtools/build/lib/buildeventstream/transports",
         "//src/main/java/com/google/devtools/build/lib/vfs",
         "//src/main/java/com/google/devtools/common/options",
+        "//third_party:auto_value",
         "//third_party:guava",
         "//third_party:jsr305",
         "//third_party/grpc:grpc-jar",
diff --git a/src/main/java/com/google/devtools/build/lib/buildeventservice/BazelBuildEventServiceModule.java b/src/main/java/com/google/devtools/build/lib/buildeventservice/BazelBuildEventServiceModule.java
index 2a85a38..1e2304b 100644
--- a/src/main/java/com/google/devtools/build/lib/buildeventservice/BazelBuildEventServiceModule.java
+++ b/src/main/java/com/google/devtools/build/lib/buildeventservice/BazelBuildEventServiceModule.java
@@ -14,12 +14,14 @@
 
 package com.google.devtools.build.lib.buildeventservice;
 
+import com.google.auto.value.AutoValue;
 import com.google.common.collect.ImmutableSet;
 import com.google.devtools.build.lib.authandtls.AuthAndTLSOptions;
 import com.google.devtools.build.lib.authandtls.GoogleAuthUtils;
 import com.google.devtools.build.lib.buildeventservice.client.BuildEventServiceClient;
 import com.google.devtools.build.lib.buildeventservice.client.ManagedBuildEventServiceGrpcClient;
 import java.io.IOException;
+import java.util.Objects;
 import java.util.Set;
 
 /**
@@ -28,17 +30,45 @@
 public class BazelBuildEventServiceModule
     extends BuildEventServiceModule<BuildEventServiceOptions> {
 
+  @AutoValue
+  abstract static class BackendConfig {
+    abstract String besBackend();
+
+    abstract AuthAndTLSOptions authAndTLSOptions();
+  }
+
+  private BuildEventServiceClient client;
+  private BackendConfig config;
+
   @Override
   protected Class<BuildEventServiceOptions> optionsClass() {
     return BuildEventServiceOptions.class;
   }
 
   @Override
-  protected BuildEventServiceClient createBesClient(BuildEventServiceOptions besOptions,
-      AuthAndTLSOptions authAndTLSOptions) throws IOException {
-    return new ManagedBuildEventServiceGrpcClient(
-        GoogleAuthUtils.newChannel(besOptions.besBackend, authAndTLSOptions),
-        GoogleAuthUtils.newCallCredentials(authAndTLSOptions));
+  protected BuildEventServiceClient getBesClient(
+      BuildEventServiceOptions besOptions, AuthAndTLSOptions authAndTLSOptions) throws IOException {
+    BackendConfig newConfig =
+        new AutoValue_BazelBuildEventServiceModule_BackendConfig(
+            besOptions.besBackend, authAndTLSOptions);
+    if (client == null || !Objects.equals(config, newConfig)) {
+      clearBesClient();
+      config = newConfig;
+      client =
+          new ManagedBuildEventServiceGrpcClient(
+              GoogleAuthUtils.newChannel(besOptions.besBackend, authAndTLSOptions),
+              GoogleAuthUtils.newCallCredentials(authAndTLSOptions));
+    }
+    return client;
+  }
+
+  @Override
+  protected void clearBesClient() {
+    if (client != null) {
+      client.shutdown();
+    }
+    this.client = null;
+    this.config = null;
   }
 
   private static final ImmutableSet<String> WHITELISTED_COMMANDS =
diff --git a/src/main/java/com/google/devtools/build/lib/buildeventservice/BuildEventServiceModule.java b/src/main/java/com/google/devtools/build/lib/buildeventservice/BuildEventServiceModule.java
index f933670..4582d0e 100644
--- a/src/main/java/com/google/devtools/build/lib/buildeventservice/BuildEventServiceModule.java
+++ b/src/main/java/com/google/devtools/build/lib/buildeventservice/BuildEventServiceModule.java
@@ -63,6 +63,7 @@
 
   private OutErr outErr;
   private BuildEventStreamer streamer;
+  private boolean keepClient;
 
   /** Whether an error in the Build Event Service upload causes the build to fail. */
   protected boolean errorsShouldFailTheBuild() {
@@ -95,6 +96,7 @@
       return;
     }
 
+    this.keepClient = false;
     streamer = tryCreateStreamer(commandEnvironment);
     if (streamer != null) {
       commandEnvironment.getReporter().addHandler(streamer);
@@ -137,6 +139,9 @@
 
   @Override
   public void afterCommand() {
+    if (!keepClient) {
+      clearBesClient();
+    }
     this.outErr = null;
     this.streamer = null;
   }
@@ -156,9 +161,14 @@
             env.getReporter(),
             env.getBlazeModuleEnvironment(),
             new AbruptExitException(message, ExitCode.PUBLISH_ERROR, e));
+        clearBesClient();
         return null;
       }
 
+      BuildEventStreamOptions buildEventStreamOptions =
+          env.getOptions().getOptions(BuildEventStreamOptions.class);
+      this.keepClient = buildEventStreamOptions.keepBackendConnections;
+
       ImmutableSet<BuildEventTransport> bepTransports =
           BuildEventTransportFactory.createFromOptions(env, env.getBlazeModuleEnvironment()::exit);
 
@@ -170,8 +180,6 @@
 
       ImmutableSet<BuildEventTransport> transports = transportsBuilder.build();
       if (!transports.isEmpty()) {
-        BuildEventStreamOptions buildEventStreamOptions =
-            env.getOptions().getOptions(BuildEventStreamOptions.class);
         return new BuildEventStreamer(transports, env.getReporter(), buildEventStreamOptions);
       }
     } catch (Exception e) {
@@ -201,6 +209,7 @@
 
     if (isNullOrEmpty(besOptions.besBackend)) {
       logger.fine("BuildEventServiceTransport is disabled.");
+      clearBesClient();
       return null;
     } else {
       logger.fine(
@@ -226,7 +235,7 @@
                         besOptions.besBackend, env.getBuildRequestId(), invocationId)));
       }
 
-      BuildEventServiceClient client = createBesClient(besOptions, authTlsOptions);
+      BuildEventServiceClient client = getBesClient(besOptions, authTlsOptions);
       BuildEventArtifactUploader artifactUploader =
           env.getRuntime()
               .getBuildEventArtifactUploaderFactoryMap()
@@ -272,10 +281,12 @@
 
   protected abstract Class<T> optionsClass();
 
-  protected abstract BuildEventServiceClient createBesClient(
+  protected abstract BuildEventServiceClient getBesClient(
       T besOptions, AuthAndTLSOptions authAndTLSOptions)
       throws IOException, OptionsParsingException;
 
+  protected abstract void clearBesClient();
+
   protected abstract Set<String> whitelistedCommands();
 
   protected Set<String> keywords(
diff --git a/src/main/java/com/google/devtools/build/lib/buildeventservice/BuildEventServiceTransport.java b/src/main/java/com/google/devtools/build/lib/buildeventservice/BuildEventServiceTransport.java
index 6fae0e2..4283043 100644
--- a/src/main/java/com/google/devtools/build/lib/buildeventservice/BuildEventServiceTransport.java
+++ b/src/main/java/com/google/devtools/build/lib/buildeventservice/BuildEventServiceTransport.java
@@ -423,11 +423,7 @@
         logError(e, "BES upload failed due to a RuntimeException. This is a bug.");
         throw e;
       } finally {
-        try {
-          besClient.shutdown();
-        } finally {
-          localFileUploader.shutdown();
-        }
+        localFileUploader.shutdown();
       }
     }
 
diff --git a/src/main/java/com/google/devtools/build/lib/buildeventstream/transports/BuildEventStreamOptions.java b/src/main/java/com/google/devtools/build/lib/buildeventstream/transports/BuildEventStreamOptions.java
index 9e0c277..4da6c78 100644
--- a/src/main/java/com/google/devtools/build/lib/buildeventstream/transports/BuildEventStreamOptions.java
+++ b/src/main/java/com/google/devtools/build/lib/buildeventstream/transports/BuildEventStreamOptions.java
@@ -17,6 +17,7 @@
 import com.google.devtools.common.options.Option;
 import com.google.devtools.common.options.OptionDocumentationCategory;
 import com.google.devtools.common.options.OptionEffectTag;
+import com.google.devtools.common.options.OptionMetadataTag;
 import com.google.devtools.common.options.OptionsBase;
 
 /** Options used to configure BuildEventStreamer and its BuildEventTransports. */
@@ -33,6 +34,15 @@
   public String buildEventTextFile;
 
   @Option(
+      name = "keep_backend_build_event_connections_alive",
+      defaultValue = "true",
+      metadataTags = {OptionMetadataTag.HIDDEN},
+      documentationCategory = OptionDocumentationCategory.UNDOCUMENTED,
+      effectTags = {OptionEffectTag.UNKNOWN},
+      help = "If enabled, keep connections to build event backend connections alive across builds.")
+  public boolean keepBackendConnections;
+
+  @Option(
       name = "build_event_binary_file",
       oldName = "experimental_build_event_binary_file",
       defaultValue = "",