Distinguish between threads paused during initialization, and threads paused in response to a explicit PauseThreadsRequest.

Makes client-side synchronization much easier.

This leaves b/112848203 unfixed for now -- I'll look at that separately

PiperOrigin-RevId: 209487111
diff --git a/src/main/java/com/google/devtools/build/lib/skylarkdebug/proto/skylark_debugging.proto b/src/main/java/com/google/devtools/build/lib/skylarkdebug/proto/skylark_debugging.proto
index a3df9e4..d3d1719 100644
--- a/src/main/java/com/google/devtools/build/lib/skylarkdebug/proto/skylark_debugging.proto
+++ b/src/main/java/com/google/devtools/build/lib/skylarkdebug/proto/skylark_debugging.proto
@@ -284,25 +284,29 @@
   Error conditional_breakpoint_error = 5;
 }
 
-// The reason why a thread was paused
+// The reason why a thread was paused.
 enum PauseReason {
-  // The debug server hasn't set a reason for pausing a thread.
+  // The debug server hasn't set any reason.
   UNSET = 0;
 
   // The stepping condition in a ContinueExecutionRequest was hit.
   STEPPING = 1;
 
-  // All threads are paused (e.g. prior to the initial StartDebuggingRequest).
+  // A PauseThreadRequest was sent with thread_id=0.
   ALL_THREADS_PAUSED = 2;
 
-  // Thread paused in response to a PauseThreadRequest.
+  // A PauseThreadRequest was sent with thread_id matching this thread.
   PAUSE_THREAD_REQUEST = 3;
 
   // A breakpoint was hit.
   HIT_BREAKPOINT = 4;
 
-  // An error occurred evaluating a breakpoint condition
+  // An error occurred while evaluating a breakpoint condition.
   CONDITIONAL_BREAKPOINT_ERROR = 5;
+
+  // Debugging just started, and a StartDebuggingRequest has not yet been
+  // received and processed.
+  INITIALIZING = 6;
 }
 
 // The debugger representation of a Skylark value.
diff --git a/src/main/java/com/google/devtools/build/lib/skylarkdebug/server/ThreadHandler.java b/src/main/java/com/google/devtools/build/lib/skylarkdebug/server/ThreadHandler.java
index b2828f8..7675b52 100644
--- a/src/main/java/com/google/devtools/build/lib/skylarkdebug/server/ThreadHandler.java
+++ b/src/main/java/com/google/devtools/build/lib/skylarkdebug/server/ThreadHandler.java
@@ -77,13 +77,15 @@
     }
   }
 
-  /**
-   * If true, all threads will pause at the earliest possible opportunity. New threads will also be
-   * immediately paused upon creation.
-   *
-   * <p>The debugger starts with all threads paused, until a StartDebuggingRequest is received.
-   */
-  private volatile boolean pausingAllThreads = true;
+  /** Whether threads are globally paused, and if so, why. */
+  private enum DebuggerState {
+    INITIALIZING, // no StartDebuggingRequest has yet been received; all threads are paused
+    ALL_THREADS_PAUSED, // all threads are paused in response to a PauseThreadRequest with id=0
+    RUNNING, // normal running: threads are not globally paused
+  }
+
+  /** The debugger starts with all threads paused, until a StartDebuggingRequest is received. */
+  private volatile DebuggerState debuggerState = DebuggerState.INITIALIZING;
 
   /** A map from identifiers of paused threads to their state info. */
   @GuardedBy("this")
@@ -114,7 +116,7 @@
 
   /** Mark all current and future threads paused. Will take effect asynchronously. */
   void pauseAllThreads() {
-    pausingAllThreads = true;
+    debuggerState = DebuggerState.ALL_THREADS_PAUSED;
   }
 
   /** Mark the given thread paused. Will take effect asynchronously. */
@@ -152,7 +154,7 @@
    */
   void resumeAllThreads() {
     threadsToPause.clear();
-    pausingAllThreads = false;
+    debuggerState = DebuggerState.RUNNING;
     synchronized (this) {
       for (PausedThreadState thread : ImmutableList.copyOf(pausedThreads.values())) {
         // continue-all doesn't support stepping.
@@ -163,14 +165,14 @@
   }
 
   /**
-   * Unpauses the given thread if it is currently paused. Also unsets {@link #pausingAllThreads}. If
-   * the thread is not paused, but currently stepping, it clears the stepping behavior so it will
-   * run unconditionally.
+   * Unpauses the given thread if it is currently paused. Also sets {@link #debuggerState} to
+   * RUNNING. If the thread is not paused, but currently stepping, it clears the stepping behavior
+   * so it will run unconditionally.
    */
   void resumeThread(long threadId, SkylarkDebuggingProtos.Stepping stepping)
       throws DebugRequestException {
     // once the user has requested any thread be resumed, don't continue pausing future threads
-    pausingAllThreads = false;
+    debuggerState = DebuggerState.RUNNING;
     synchronized (this) {
       threadsToPause.remove(threadId);
       if (steppingThreads.remove(threadId) != null) {
@@ -326,9 +328,13 @@
   private PauseReason shouldPauseCurrentThread(Environment env, Location location)
       throws ConditionalBreakpointException {
     long threadId = Thread.currentThread().getId();
-    if (pausingAllThreads) {
+    DebuggerState state = debuggerState;
+    if (state == DebuggerState.ALL_THREADS_PAUSED) {
       return PauseReason.ALL_THREADS_PAUSED;
     }
+    if (state == DebuggerState.INITIALIZING) {
+      return PauseReason.INITIALIZING;
+    }
     if (threadsToPause.contains(threadId)) {
       return PauseReason.PAUSE_THREAD_REQUEST;
     }
diff --git a/src/test/java/com/google/devtools/build/lib/skylarkdebug/server/SkylarkDebugServerTest.java b/src/test/java/com/google/devtools/build/lib/skylarkdebug/server/SkylarkDebugServerTest.java
index b1cf3d3..6275d22 100644
--- a/src/test/java/com/google/devtools/build/lib/skylarkdebug/server/SkylarkDebugServerTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skylarkdebug/server/SkylarkDebugServerTest.java
@@ -155,7 +155,7 @@
                 SkylarkDebuggingProtos.PausedThread.newBuilder()
                     .setId(threadId)
                     .setName(threadName)
-                    .setPauseReason(PauseReason.ALL_THREADS_PAUSED)
+                    .setPauseReason(PauseReason.INITIALIZING)
                     .setLocation(expectedLocation)
                     .build()));