ExperimentalEventHandler: add a shutdown flag

We've observed hangs of Bazel on shutdown with thread stacks indicating
that the cli-update-thread does not exit properly. We don't know why
this is happening, and have no easy way to reproduce.

My best guess is that the interrupt is swallowed somewhere, although I
was unable to pinpoint a specific place where that could happen. As a
defensive mechanism, this change adds a boolean flag that signals to the
CLI thread to shutdown in case the interrupt signal gets lost. This also
helps with a potential race between startUpdateThread and
stopUpdateThread.

Possible fix for #8432.

PiperOrigin-RevId: 249612495
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/ExperimentalEventHandler.java b/src/main/java/com/google/devtools/build/lib/runtime/ExperimentalEventHandler.java
index e8753fa..f990bf0 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/ExperimentalEventHandler.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/ExperimentalEventHandler.java
@@ -112,6 +112,7 @@
   private boolean buildRunning;
   // Number of open build even protocol transports.
   private boolean progressBarNeedsRefresh;
+  private volatile boolean shutdown;
   private final AtomicReference<Thread> updateThread;
   private byte[] stdoutBuffer;
   private byte[] stderrBuffer;
@@ -990,7 +991,7 @@
           new Thread(
               () -> {
                 try {
-                  while (true) {
+                  while (!shutdown) {
                     Thread.sleep(minimalUpdateInterval);
                     if (lastRefreshMillis < mustRefreshAfterMillis
                         && mustRefreshAfterMillis < clock.currentTimeMillis()) {
@@ -1015,6 +1016,7 @@
    * NOT CALL from a SYNCHRONIZED block, as this will give the opportunity for dead locks.
    */
   private void stopUpdateThread() {
+    shutdown = true;
     Thread threadToWaitFor = updateThread.getAndSet(null);
     if (threadToWaitFor != null) {
       threadToWaitFor.interrupt();