[dds/dap] Don't handle isolate startup until isolate is in PauseStart state

While investigating the flaky test at https://github.com/dart-lang/sdk/issues/60187#issuecomment-3431336127 I found that we had two paths into `handleThreadStartup` - one which explicitly sent a "pause-on-entry" event, and one that did not. The method sets a flag so it only ever runs once.

This means depending on which instance of `handleThreadStartup` occurs, the client may or may not get the pause-on-entry event.

The case where it would is where the isolate already had a pause-start event when we found it. The case where it would not, is if the isolate did not have a pause event (for example it was None because the isolate was just being created).

I can't come up with a good explanation for why we would try to pre-emptively run startup for an isolate we just discovered with no pauseEvent, so I have removed this branch on the assumption that we would always then see a `PauseStart` event for the isolate and then handled it through the same code path (and therefore, always sent the `pause-on-entry` event prior to calling readyToResume).

Fixes https://github.com/dart-lang/sdk/issues/60187

Change-Id: I213e9865480942b074d4d8470703377ea7ddb519
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/457040
Reviewed-by: Helin Shiah <helinx@google.com>
Commit-Queue: Ben Konyi <bkonyi@google.com>
Reviewed-by: Ben Konyi <bkonyi@google.com>
diff --git a/pkg/dds/CHANGELOG.md b/pkg/dds/CHANGELOG.md
index 03b0e6f..32cc8aa 100644
--- a/pkg/dds/CHANGELOG.md
+++ b/pkg/dds/CHANGELOG.md
@@ -1,3 +1,7 @@
+# 5.2.0-wip
+
+- [DAP] `Stopped(reason: 'entry')` events will no longer be lost if an isolate has not yet reached the `PauseStart` state when connecting to the VM.
+
 # 5.1.0
 - Update to version 2.1 of the DDS protocol.
 - Deprecate `getAvailableCachedCpuSamples` and `getCachedCpuSamples` RPCs.
diff --git a/pkg/dds/lib/src/dap/adapters/dart.dart b/pkg/dds/lib/src/dap/adapters/dart.dart
index a1c53b2..70bd224 100644
--- a/pkg/dds/lib/src/dap/adapters/dart.dart
+++ b/pkg/dds/lib/src/dap/adapters/dart.dart
@@ -831,21 +831,16 @@
       final pauseEventKind = isolate.runnable ?? false
           ? vm.EventKind.kIsolateRunnable
           : vm.EventKind.kIsolateStart;
-      final thread =
-          await isolateManager.registerIsolate(isolate, pauseEventKind);
+      await isolateManager.registerIsolate(isolate, pauseEventKind);
 
       // If the Isolate already has a Pause event we can give it to the
       // IsolateManager to handle (if it's PausePostStart it will re-configure
-      // the isolate before resuming), otherwise we can just resume it (if it's
-      // runnable - otherwise we'll handle this when it becomes runnable in an
-      // event later).
+      // the isolate before resuming), otherwise wait for the PauseStart event
+      // to arrive and then this will be done.
       if (isolate.pauseEvent?.kind?.startsWith('Pause') ?? false) {
         await isolateManager.handleEvent(
           isolate.pauseEvent!,
         );
-      } else if (isolate.runnable == true) {
-        await isolateManager.handleThreadStartup(thread,
-            sendStoppedOnEntry: false);
       }
     }));
   }
diff --git a/pkg/dds/lib/src/dap/isolate_manager.dart b/pkg/dds/lib/src/dap/isolate_manager.dart
index bfc2f49..aad6681 100644
--- a/pkg/dds/lib/src/dap/isolate_manager.dart
+++ b/pkg/dds/lib/src/dap/isolate_manager.dart
@@ -694,7 +694,7 @@
       thread.startupHandled = true;
       await readyToResumeThread(thread.threadId);
     } else if (eventKind == vm.EventKind.kPauseStart) {
-      handleThreadStartup(thread, sendStoppedOnEntry: true);
+      handleThreadStartup(thread);
     } else {
       // PauseExit, PauseBreakpoint, PauseInterrupted, PauseException
       var reason = 'pause';
@@ -765,10 +765,7 @@
   /// Handles thread startup if it has not already been handled.
   ///
   /// This includes sending Stopped-on-Entry and sending a readyToResume.
-  Future<void> handleThreadStartup(
-    ThreadInfo thread, {
-    required bool sendStoppedOnEntry,
-  }) async {
+  Future<void> handleThreadStartup(ThreadInfo thread) async {
     // Don't resume from a PauseStart if this has already happened (see
     // comments on [thread.startupHandled]).
     if (thread.startupHandled) {
@@ -776,12 +773,11 @@
     }
 
     thread.startupHandled = true;
+
     // Send a Stopped event to inform the client UI the thread is paused and
     // declare that we are ready to resume (which might result in an
     // immediate resume).
-    if (sendStoppedOnEntry) {
-      sendStoppedOnEntryEvent(thread);
-    }
+    sendStoppedOnEntryEvent(thread);
     await readyToResumeThread(thread.threadId);
   }
 
diff --git a/pkg/dds/pubspec.yaml b/pkg/dds/pubspec.yaml
index dd8c6b0..511443d 100644
--- a/pkg/dds/pubspec.yaml
+++ b/pkg/dds/pubspec.yaml
@@ -1,5 +1,5 @@
 name: dds
-version: 5.1.0
+version: 5.2.0-wip
 description: >-
   A library used to spawn the Dart Developer Service, used to communicate with
   a Dart VM Service instance.