[ Service ] Wait for DDS connection before outputting service connection
information

When dartdev and the VM service are enabled, the service should avoid
advertising its connection information until after DDS has connected in
order to prevent race conditions between DDS and external clients.

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

Change-Id: If6393f085e9147af628997a1f8936e84f9d8310c
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/157462
Reviewed-by: Siva Annamalai <asiva@google.com>
Commit-Queue: Ben Konyi <bkonyi@google.com>
diff --git a/pkg/dartdev/lib/src/commands/run.dart b/pkg/dartdev/lib/src/commands/run.dart
index 8768762..31bd2ba 100644
--- a/pkg/dartdev/lib/src/commands/run.dart
+++ b/pkg/dartdev/lib/src/commands/run.dart
@@ -209,9 +209,8 @@
     // service intermediary which implements the VM service protocol and
     // provides non-VM specific extensions (e.g., log caching, client
     // synchronization).
-    // TODO(bkonyi): Handle race condition made possible by Observatory
-    // listening message being printed to console before DDS is started.
-    // See https://github.com/dart-lang/sdk/issues/42727
+    // TODO(bkonyi): Remove once DevTools supports DDS.
+    // See https://github.com/flutter/flutter/issues/62507
     launchDds = false;
     _DebuggingSession debugSession;
     if (launchDds) {
diff --git a/runtime/bin/dart_embedder_api_impl.cc b/runtime/bin/dart_embedder_api_impl.cc
index ab0cbe0..88c641f 100644
--- a/runtime/bin/dart_embedder_api_impl.cc
+++ b/runtime/bin/dart_embedder_api_impl.cc
@@ -107,7 +107,8 @@
                              config.disable_auth_codes,
                              config.write_service_info_filename,
                              /*trace_loading=*/false, config.deterministic,
-                             /*enable_service_port_fallback=*/false)) {
+                             /*enable_service_port_fallback=*/false,
+                             /*wait_for_dds_to_advertise_service=*/false)) {
     *error = Utils::StrDup(bin::VmService::GetErrorMessage());
     return nullptr;
   }
@@ -142,7 +143,8 @@
                              config.disable_auth_codes,
                              config.write_service_info_filename,
                              /*trace_loading=*/false, config.deterministic,
-                             /*enable_service_port_fallback=*/false)) {
+                             /*enable_service_port_fallback=*/false,
+                             /*wait_for_dds_to_advertise_service=*/false)) {
     *error = Utils::StrDup(bin::VmService::GetErrorMessage());
     return nullptr;
   }
diff --git a/runtime/bin/main.cc b/runtime/bin/main.cc
index 9457dcd..000855d 100644
--- a/runtime/bin/main.cc
+++ b/runtime/bin/main.cc
@@ -549,7 +549,10 @@
           Options::vm_service_server_ip(), Options::vm_service_server_port(),
           Options::vm_service_dev_mode(), Options::vm_service_auth_disabled(),
           Options::vm_write_service_info_filename(), Options::trace_loading(),
-          Options::deterministic(), Options::enable_service_port_fallback())) {
+          Options::deterministic(), Options::enable_service_port_fallback(),
+          // TODO(bkonyi): uncomment when DDS is re-enabled.
+          // See https://github.com/flutter/flutter/issues/62507
+          /*!Options::disable_dart_dev()*/ false)) {
     *error = Utils::StrDup(VmService::GetErrorMessage());
     return NULL;
   }
diff --git a/runtime/bin/run_vm_tests.cc b/runtime/bin/run_vm_tests.cc
index c288359..9513513 100644
--- a/runtime/bin/run_vm_tests.cc
+++ b/runtime/bin/run_vm_tests.cc
@@ -149,7 +149,8 @@
                              /*dev_mode=*/false, /*auth_disabled=*/true,
                              /*write_service_info_filename*/ "",
                              /*trace_loading=*/false, /*deterministic=*/true,
-                             /*enable_service_port_fallback=*/false)) {
+                             /*enable_service_port_fallback=*/false,
+                             /*wait_for_dds_to_advertise_service*/ false)) {
     *error = Utils::StrDup(bin::VmService::GetErrorMessage());
     return nullptr;
   }
diff --git a/runtime/bin/vmservice_impl.cc b/runtime/bin/vmservice_impl.cc
index 0a1896a..47b7992 100644
--- a/runtime/bin/vmservice_impl.cc
+++ b/runtime/bin/vmservice_impl.cc
@@ -118,7 +118,8 @@
                       const char* write_service_info_filename,
                       bool trace_loading,
                       bool deterministic,
-                      bool enable_service_port_fallback) {
+                      bool enable_service_port_fallback,
+                      bool wait_for_dds_to_advertise_service) {
   Dart_Isolate isolate = Dart_CurrentIsolate();
   ASSERT(isolate != NULL);
   SetServerAddress("");
@@ -189,6 +190,12 @@
                                        write_service_info_filename);
     SHUTDOWN_ON_ERROR(result);
   }
+
+  result = Dart_SetField(library,
+                         DartUtils::NewString("_waitForDdsToAdvertiseService"),
+                         Dart_NewBoolean(wait_for_dds_to_advertise_service));
+  SHUTDOWN_ON_ERROR(result);
+
 // Are we running on Windows?
 #if defined(HOST_OS_WINDOWS)
   Dart_Handle is_windows = Dart_True();
diff --git a/runtime/bin/vmservice_impl.h b/runtime/bin/vmservice_impl.h
index 4b5838b..3371f27 100644
--- a/runtime/bin/vmservice_impl.h
+++ b/runtime/bin/vmservice_impl.h
@@ -21,7 +21,8 @@
                     const char* write_service_info_filename,
                     bool trace_loading,
                     bool deterministic,
-                    bool enable_service_port_fallback);
+                    bool enable_service_port_fallback,
+                    bool wait_for_dds_to_advertise_service);
 
   static void SetNativeResolver();
 
diff --git a/sdk/lib/_internal/vm/bin/vmservice_io.dart b/sdk/lib/_internal/vm/bin/vmservice_io.dart
index e84c8f8..5718d46 100644
--- a/sdk/lib/_internal/vm/bin/vmservice_io.dart
+++ b/sdk/lib/_internal/vm/bin/vmservice_io.dart
@@ -37,6 +37,8 @@
 var _signalSubscription;
 @pragma("vm:entry-point")
 bool _enableServicePortFallback = false;
+@pragma("vm:entry-point")
+bool _waitForDdsToAdvertiseService = false;
 
 // HTTP server.
 Server? server;
@@ -78,6 +80,12 @@
   _shutdown();
 }
 
+Future<void> ddsConnectedCallback() async {
+  if (_waitForDdsToAdvertiseService) {
+    await server!.outputConnectionInformation();
+  }
+}
+
 Future<Uri> createTempDirCallback(String base) async {
   final temp = await Directory.systemTemp.createTemp(base);
   // Underneath the temporary directory, create a directory with the
@@ -245,6 +253,7 @@
   // Set embedder hooks.
   VMServiceEmbedderHooks.cleanup = cleanupCallback;
   VMServiceEmbedderHooks.createTempDir = createTempDirCallback;
+  VMServiceEmbedderHooks.ddsConnected = ddsConnectedCallback;
   VMServiceEmbedderHooks.deleteDir = deleteDirCallback;
   VMServiceEmbedderHooks.writeFile = writeFileCallback;
   VMServiceEmbedderHooks.writeStreamFile = writeStreamFileCallback;
diff --git a/sdk/lib/_internal/vm/bin/vmservice_server.dart b/sdk/lib/_internal/vm/bin/vmservice_server.dart
index 68e18a3..09ec391 100644
--- a/sdk/lib/_internal/vm/bin/vmservice_server.dart
+++ b/sdk/lib/_internal/vm/bin/vmservice_server.dart
@@ -477,11 +477,21 @@
     }
     final server = _server!;
     server.listen(_requestHandler, cancelOnError: true);
+    if (!_waitForDdsToAdvertiseService) {
+      await outputConnectionInformation();
+    }
+    // Server is up and running.
+    _notifyServerState(serverAddress.toString());
+    onServerAddressChange('$serverAddress');
+    return this;
+  }
+
+  Future<void> outputConnectionInformation() async {
     serverPrint('Observatory listening on $serverAddress');
     if (Platform.isFuchsia) {
       // Create a file with the port number.
       final tmp = Directory.systemTemp.path;
-      final path = '$tmp/dart.services/${server.port}';
+      final path = '$tmp/dart.services/${_server!.port}';
       serverPrint('Creating $path');
       File(path)..createSync(recursive: true);
     }
@@ -490,10 +500,6 @@
         serviceInfoFilenameLocal.isNotEmpty) {
       await _dumpServiceInfoToFile(serviceInfoFilenameLocal);
     }
-    // Server is up and running.
-    _notifyServerState(serverAddress.toString());
-    onServerAddressChange('$serverAddress');
-    return this;
   }
 
   Future<void> cleanup(bool force) {
diff --git a/sdk/lib/vmservice/vmservice.dart b/sdk/lib/vmservice/vmservice.dart
index 840a6e0..c404a78 100644
--- a/sdk/lib/vmservice/vmservice.dart
+++ b/sdk/lib/vmservice/vmservice.dart
@@ -139,6 +139,9 @@
 /// Called when the server should be stopped.
 typedef Future ServerStopCallback();
 
+/// Called when DDS has connected.
+typedef Future<void> DdsConnectedCallback();
+
 /// Called when the service is exiting.
 typedef Future CleanupCallback();
 
@@ -177,6 +180,7 @@
 class VMServiceEmbedderHooks {
   static ServerStartCallback? serverStart;
   static ServerStopCallback? serverStop;
+  static DdsConnectedCallback? ddsConnected;
   static CleanupCallback? cleanup;
   static CreateTempDirCallback? createTempDir;
   static DeleteDirCallback? deleteDir;
@@ -245,6 +249,7 @@
     }
     acceptNewWebSocketConnections(false);
     _ddsUri = Uri.parse(uri);
+    await VMServiceEmbedderHooks?.ddsConnected!();
     return encodeSuccess(message);
   }