Fix one check-then-act race in ExperimentalStateTracker#writeProgressBar.

#sampleOldestActions gets called via #writeProgressBar if #writeProgressBar observed 'activeActions.size() > 1'. But the 'activeActions' map may be empty by the time 'activeActions.size()' is evaluated again in #sampleOldestActions. In this case, we invoke the PriorityQueue ctor with an initialCapacity of 0 and the ctor throws an IllegalArgumentException.

RELNOTES: None
PiperOrigin-RevId: 257435797
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/ExperimentalStateTracker.java b/src/main/java/com/google/devtools/build/lib/runtime/ExperimentalStateTracker.java
index a2ba9ed..561aac7 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/ExperimentalStateTracker.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/ExperimentalStateTracker.java
@@ -798,21 +798,29 @@
   }
 
   private void sampleOldestActions(AnsiTerminalWriter terminalWriter) throws IOException {
-    int count = 0;
-    int totalCount = 0;
-    long nanoTime = clock.nanoTime();
-    int actionCount = activeActions.size();
-    Set<Artifact> toSkip = new HashSet<>();
-
+    // This method can only be called if 'activeActions.size()' was observed to be larger than 1 at
+    // some point in the past. But the 'activeActions' map can grow and shrink concurrent with this
+    // code here so we need to be very careful lest we fall victim to a check-then-act race.
+    int racyActiveActionsCount = activeActions.size();
     PriorityQueue<Map.Entry<Artifact, ActionState>> priorityHeap =
         new PriorityQueue<>(
-            activeActions.size(),
+            // The 'initialCapacity' parameter must be positive.
+            /*initialCapacity=*/ Math.max(racyActiveActionsCount, 1),
             Comparator.comparing(
                     (Map.Entry<Artifact, ActionState> entry) ->
                         entry.getValue().runningStrategiesBitmap == 0)
                 .thenComparingLong(entry -> entry.getValue().nanoStartTime)
                 .thenComparingInt(entry -> entry.getValue().hashCode()));
     priorityHeap.addAll(activeActions.entrySet());
+    // From this point on, we no longer consume 'activeActions'. So now it's sound to look at how
+    // many entries were added to 'priorityHeap' and act appropriately based on that count.
+    int actualObservedActiveActionsCount = priorityHeap.size();
+
+    int count = 0;
+    int totalCount = 0;
+    long nanoTime = clock.nanoTime();
+    Set<Artifact> toSkip = new HashSet<>();
+
     while (!priorityHeap.isEmpty()) {
       Map.Entry<Artifact, ActionState> entry = priorityHeap.poll();
       totalCount++;
@@ -825,12 +833,16 @@
         break;
       }
       int width =
-          targetWidth - 4 - ((count >= sampleSize && count < actionCount) ? AND_MORE.length() : 0);
+          targetWidth
+              - 4
+              - ((count >= sampleSize && count < actualObservedActiveActionsCount)
+                  ? AND_MORE.length()
+                  : 0);
       terminalWriter
           .newline()
           .append("    " + describeAction(entry.getValue(), nanoTime, width, toSkip));
     }
-    if (totalCount < actionCount) {
+    if (totalCount < actualObservedActiveActionsCount) {
       terminalWriter.append(AND_MORE);
     }
   }