Reland "[ Service / DDS ] Advertise DDS as the VM service, bump version to 4.0"

This reverts commit ed120c3c800a80ed33d11bb53dc1700128d2de20.

Change-Id: I445f4bb2dafaad3ce2daa3ae42efe1723f9b1abe
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/160660
Reviewed-by: Siva Annamalai <asiva@google.com>
Commit-Queue: Ben Konyi <bkonyi@google.com>
diff --git a/pkg/dartdev/lib/dartdev.dart b/pkg/dartdev/lib/dartdev.dart
index 1e0af75..4cc2964 100644
--- a/pkg/dartdev/lib/dartdev.dart
+++ b/pkg/dartdev/lib/dartdev.dart
@@ -81,9 +81,15 @@
 
   // --launch-dds is provided by the VM if the VM service is to be enabled. In
   // that case, we need to launch DDS as well.
-  // TODO(bkonyi): add support for pub run (#42726)
-  if (args.contains('--launch-dds')) {
+  final launchDdsArg = args.singleWhere(
+    (element) => element.startsWith('--launch-dds'),
+    orElse: () => null,
+  );
+  if (launchDdsArg != null) {
     RunCommand.launchDds = true;
+    final ddsUrl = (launchDdsArg.split('=')[1]).split(':');
+    RunCommand.ddsHost = ddsUrl[0];
+    RunCommand.ddsPort = ddsUrl[1];
   }
   String commandName;
 
@@ -98,8 +104,8 @@
 
     // Run also can't be called with '--launch-dds', remove it if it's
     // contained in args.
-    if (args.contains('--launch-dds')) {
-      args = List.from(args)..remove('--launch-dds');
+    if (launchDdsArg != null) {
+      args = List.from(args)..remove(launchDdsArg);
     }
 
     // These flags have a format that can't be handled by package:args, so
diff --git a/pkg/dartdev/lib/src/commands/run.dart b/pkg/dartdev/lib/src/commands/run.dart
index 8f28c9f..feeb99c 100644
--- a/pkg/dartdev/lib/src/commands/run.dart
+++ b/pkg/dartdev/lib/src/commands/run.dart
@@ -18,6 +18,8 @@
 
 class RunCommand extends DartdevCommand<int> {
   static bool launchDds = false;
+  static String ddsHost;
+  static String ddsPort;
 
   // kErrorExitCode, as defined in runtime/bin/error_exit.h
   static const errorExitCode = 255;
@@ -209,9 +211,6 @@
     // service intermediary which implements the VM service protocol and
     // provides non-VM specific extensions (e.g., log caching, client
     // synchronization).
-    // TODO(bkonyi): Remove once DevTools supports DDS.
-    // See https://github.com/flutter/flutter/issues/62507
-    launchDds = false;
     _DebuggingSession debugSession;
     if (launchDds) {
       debugSession = _DebuggingSession();
@@ -255,7 +254,9 @@
             sdk.ddsSnapshot
           else
             absolute(dirname(sdk.dart), 'gen', 'dds.dart.snapshot'),
-          serviceInfo.serverUri.toString()
+          serviceInfo.serverUri.toString(),
+          RunCommand.ddsHost,
+          RunCommand.ddsPort,
         ],
         mode: ProcessStartMode.detachedWithStdio);
     final completer = Completer<void>();
@@ -264,10 +265,20 @@
       if (event == 'DDS started') {
         sub.cancel();
         completer.complete();
+      } else if (event.contains('Failed to start DDS')) {
+        sub.cancel();
+        completer.completeError(event.replaceAll(
+          'Failed to start DDS',
+          'Could not start Observatory HTTP server',
+        ));
       }
     });
-
-    await completer.future;
-    return true;
+    try {
+      await completer.future;
+      return true;
+    } catch (e) {
+      stderr.write(e);
+      return false;
+    }
   }
 }
diff --git a/pkg/dds/bin/dds.dart b/pkg/dds/bin/dds.dart
index 476e84b..ed683dd 100644
--- a/pkg/dds/bin/dds.dart
+++ b/pkg/dds/bin/dds.dart
@@ -9,10 +9,39 @@
 /// A simple program which starts a [DartDevelopmentService] instance with a
 /// basic configuration.
 ///
-/// Takes the VM service URI as its single argument.
+/// Takes the following positional arguments:
+///   - VM service URI
+///   - DDS bind address
+///   - DDS port
 Future<void> main(List<String> args) async {
   if (args.isEmpty) return;
+
+  // This URI is provided by the VM service directly so don't bother doing a
+  // lookup.
   final remoteVmServiceUri = Uri.parse(args.first);
-  await DartDevelopmentService.startDartDevelopmentService(remoteVmServiceUri);
-  stderr.write('DDS started');
+
+  // Resolve the address which is potentially provided by the user.
+  InternetAddress address;
+  final addresses = await InternetAddress.lookup(args[1]);
+  // Prefer IPv4 addresses.
+  for (int i = 0; i < addresses.length; i++) {
+    address = addresses[i];
+    if (address.type == InternetAddressType.IPv4) break;
+  }
+  final serviceUri = Uri(
+    scheme: 'http',
+    host: address.address,
+    port: int.parse(args[2]),
+  );
+  try {
+    // TODO(bkonyi): add retry logic similar to that in vmservice_server.dart
+    // See https://github.com/dart-lang/sdk/issues/43192.
+    await DartDevelopmentService.startDartDevelopmentService(
+      remoteVmServiceUri,
+      serviceUri: serviceUri,
+    );
+    stderr.write('DDS started');
+  } catch (e) {
+    stderr.writeln('Failed to start DDS:\n$e');
+  }
 }
diff --git a/pkg/dds/test/auth_codes_test.dart b/pkg/dds/test/auth_codes_test.dart
index 9e7ad8c..653ff41 100644
--- a/pkg/dds/test/auth_codes_test.dart
+++ b/pkg/dds/test/auth_codes_test.dart
@@ -37,7 +37,7 @@
       final service = await vmServiceConnectUri(dds.wsUri.toString());
       final version = await service.getVersion();
       expect(version.major > 0, true);
-      expect(version.minor > 0, true);
+      expect(version.minor >= 0, true);
 
       // Ensure we can still make requests of the VM service via HTTP.
       HttpClient client = HttpClient();
diff --git a/pkg/dds/test/smoke_test.dart b/pkg/dds/test/smoke_test.dart
index e7e5216..0da26db 100644
--- a/pkg/dds/test/smoke_test.dart
+++ b/pkg/dds/test/smoke_test.dart
@@ -41,7 +41,7 @@
           final service = await vmServiceConnectUri(dds.wsUri.toString());
           final version = await service.getVersion();
           expect(version.major > 0, true);
-          expect(version.minor > 0, true);
+          expect(version.minor >= 0, true);
 
           expect(
             dds.uri.pathSegments,
@@ -64,7 +64,7 @@
               .single);
           expect(jsonResponse['result']['type'], 'Version');
           expect(jsonResponse['result']['major'] > 0, true);
-          expect(jsonResponse['result']['minor'] > 0, true);
+          expect(jsonResponse['result']['minor'] >= 0, true);
         },
       );
     }
diff --git a/pkg/vm_service/CHANGELOG.md b/pkg/vm_service/CHANGELOG.md
index 7be2213..466d85f 100644
--- a/pkg/vm_service/CHANGELOG.md
+++ b/pkg/vm_service/CHANGELOG.md
@@ -1,6 +1,11 @@
 # Changelog
 
-## Unreleased
+## 5.0.0
+
+- **breaking**: Update to version `4.0.0` of the spec.
+  - Removes `ClientName` and `WebSocketTarget` objects
+  - Removes `getClientName`, `getWebSocketTarget`, `requirePermissionToResume`,
+    and `setClientName` RPCs.
 - Added `isSystemIsolate` property to `IsolateRef` and `Isolate`.
 - Added `isSystemIsolateGroup` property to `IsolateGroupRef` and `IsolateGroup`.
 - Added `serviceIsolates` and `serviceIsolateGroups` properties to `VM`.
diff --git a/pkg/vm_service/example/vm_service_assert.dart b/pkg/vm_service/example/vm_service_assert.dart
index 4dc33b5..52746bd 100644
--- a/pkg/vm_service/example/vm_service_assert.dart
+++ b/pkg/vm_service/example/vm_service_assert.dart
@@ -367,13 +367,6 @@
   return obj;
 }
 
-vms.ClientName assertClientName(vms.ClientName obj) {
-  assertNotNull(obj);
-  assertString(obj.type);
-  assertString(obj.name);
-  return obj;
-}
-
 vms.CodeRef assertCodeRef(vms.CodeRef obj) {
   assertNotNull(obj);
   assertString(obj.type);
@@ -1201,10 +1194,3 @@
   assertListOfIsolateGroupRef(obj.systemIsolateGroups);
   return obj;
 }
-
-vms.WebSocketTarget assertWebSocketTarget(vms.WebSocketTarget obj) {
-  assertNotNull(obj);
-  assertString(obj.type);
-  assertString(obj.uri);
-  return obj;
-}
diff --git a/pkg/vm_service/java/.gitignore b/pkg/vm_service/java/.gitignore
index 4370934..fbcce7d 100644
--- a/pkg/vm_service/java/.gitignore
+++ b/pkg/vm_service/java/.gitignore
@@ -5,7 +5,6 @@
 src/org/dartlang/vm/service/consumer/AddBreakpointConsumer.java
 src/org/dartlang/vm/service/consumer/AddBreakpointWithScriptUriConsumer.java
 src/org/dartlang/vm/service/consumer/ClearCpuSamplesConsumer.java
-src/org/dartlang/vm/service/consumer/ClientNameConsumer.java
 src/org/dartlang/vm/service/consumer/EvaluateConsumer.java
 src/org/dartlang/vm/service/consumer/EvaluateInFrameConsumer.java
 src/org/dartlang/vm/service/consumer/FlagListConsumer.java
@@ -42,7 +41,6 @@
 src/org/dartlang/vm/service/consumer/TimestampConsumer.java
 src/org/dartlang/vm/service/consumer/VMConsumer.java
 src/org/dartlang/vm/service/consumer/VersionConsumer.java
-src/org/dartlang/vm/service/consumer/WebSocketTargetConsumer.java
 src/org/dartlang/vm/service/element/AllocationProfile.java
 src/org/dartlang/vm/service/element/BoundField.java
 src/org/dartlang/vm/service/element/BoundVariable.java
@@ -51,7 +49,6 @@
 src/org/dartlang/vm/service/element/ClassList.java
 src/org/dartlang/vm/service/element/ClassObj.java
 src/org/dartlang/vm/service/element/ClassRef.java
-src/org/dartlang/vm/service/element/ClientName.java
 src/org/dartlang/vm/service/element/Code.java
 src/org/dartlang/vm/service/element/CodeKind.java
 src/org/dartlang/vm/service/element/CodeRef.java
@@ -129,4 +126,3 @@
 src/org/dartlang/vm/service/element/VM.java
 src/org/dartlang/vm/service/element/VMRef.java
 src/org/dartlang/vm/service/element/Version.java
-src/org/dartlang/vm/service/element/WebSocketTarget.java
diff --git a/pkg/vm_service/java/version.properties b/pkg/vm_service/java/version.properties
index 0fa4741..db1ef8e5 100644
--- a/pkg/vm_service/java/version.properties
+++ b/pkg/vm_service/java/version.properties
@@ -1 +1 @@
-version=3.38
+version=4.0
diff --git a/pkg/vm_service/lib/src/vm_service.dart b/pkg/vm_service/lib/src/vm_service.dart
index c9b4ec7..c518b25 100644
--- a/pkg/vm_service/lib/src/vm_service.dart
+++ b/pkg/vm_service/lib/src/vm_service.dart
@@ -28,7 +28,7 @@
         HeapSnapshotObjectNoData,
         HeapSnapshotObjectNullData;
 
-const String vmServiceVersion = '3.38.0';
+const String vmServiceVersion = '4.0.0';
 
 /// @optional
 const String optional = 'optional';
@@ -117,7 +117,6 @@
   'Class': Class.parse,
   'ClassHeapStats': ClassHeapStats.parse,
   'ClassList': ClassList.parse,
-  'ClientName': ClientName.parse,
   '@Code': CodeRef.parse,
   'Code': Code.parse,
   '@Context': ContextRef.parse,
@@ -186,7 +185,6 @@
   'Version': Version.parse,
   '@VM': VMRef.parse,
   'VM': VM.parse,
-  'WebSocketTarget': WebSocketTarget.parse,
 };
 
 Map<String, List<String>> _methodReturnTypes = {
@@ -200,7 +198,6 @@
   'evaluateInFrame': const ['InstanceRef', 'ErrorRef'],
   'getAllocationProfile': const ['AllocationProfile'],
   'getClassList': const ['ClassList'],
-  'getClientName': const ['ClientName'],
   'getCpuSamples': const ['CpuSamples'],
   'getFlagList': const ['FlagList'],
   'getInboundReferences': const ['InboundReferences'],
@@ -221,16 +218,13 @@
   'getVMTimeline': const ['Timeline'],
   'getVMTimelineFlags': const ['TimelineFlags'],
   'getVMTimelineMicros': const ['Timestamp'],
-  'getWebSocketTarget': const ['WebSocketTarget'],
   'pause': const ['Success'],
   'kill': const ['Success'],
   'registerService': const ['Success'],
   'reloadSources': const ['ReloadReport'],
   'removeBreakpoint': const ['Success'],
   'requestHeapSnapshot': const ['Success'],
-  'requirePermissionToResume': const ['Success'],
   'resume': const ['Success'],
-  'setClientName': const ['Success'],
   'setExceptionPauseMode': const ['Success'],
   'setFlag': const ['Success', 'Error'],
   'setLibraryDebuggable': const ['Success'],
@@ -518,13 +512,6 @@
   /// returned.
   Future<ClassList> getClassList(String isolateId);
 
-  /// The `getClientName` RPC is used to retrieve the name associated with the
-  /// currently connected VM service client. If no name was previously set
-  /// through the [setClientName] RPC, a default name will be returned.
-  ///
-  /// See [ClientName].
-  Future<ClientName> getClientName();
-
   /// The `getCpuSamples` RPC is used to retrieve samples collected by the CPU
   /// profiler. Only samples collected in the time range `[timeOriginMicros,
   /// timeOriginMicros + timeExtentMicros]` will be reported.
@@ -854,13 +841,6 @@
   /// See [Timestamp] and [getVMTimeline].
   Future<Timestamp> getVMTimelineMicros();
 
-  /// The `getWebSocketTarget` RPC returns the web socket URI that should be
-  /// used by VM service clients with WebSocket implementations that do not
-  /// follow redirects (e.g., `dart:html`'s [WebSocket]).
-  ///
-  /// See [WebSocketTarget].
-  Future<WebSocketTarget> getWebSocketTarget();
-
   /// The `pause` RPC is used to interrupt a running isolate. The RPC enqueues
   /// the interrupt request and potentially returns before the isolate is
   /// paused.
@@ -956,39 +936,6 @@
   /// returned.
   Future<Success> requestHeapSnapshot(String isolateId);
 
-  /// The `requirePermissionToResume` RPC is used to change the pause/resume
-  /// behavior of isolates by providing a way for the VM service to wait for
-  /// approval to resume from some set of clients. This is useful for clients
-  /// which want to perform some operation on an isolate after a pause without
-  /// it being resumed by another client.
-  ///
-  /// If the `onPauseStart` parameter is `true`, isolates will not resume after
-  /// pausing on start until the client sends a `resume` request and all other
-  /// clients which need to provide resume approval for this pause type have
-  /// done so.
-  ///
-  /// If the `onPauseReload` parameter is `true`, isolates will not resume after
-  /// pausing after a reload until the client sends a `resume` request and all
-  /// other clients which need to provide resume approval for this pause type
-  /// have done so.
-  ///
-  /// If the `onPauseExit` parameter is `true`, isolates will not resume after
-  /// pausing on exit until the client sends a `resume` request and all other
-  /// clients which need to provide resume approval for this pause type have
-  /// done so.
-  ///
-  /// **Important Notes:**
-  ///
-  /// - All clients with the same client name share resume permissions. Only a
-  /// single client of a given name is required to provide resume approval.
-  /// - When a client requiring approval disconnects from the service, a paused
-  /// isolate may resume if all other clients requiring resume approval have
-  /// already given approval. In the case that no other client requires resume
-  /// approval for the current pause event, the isolate will be resumed if at
-  /// least one other client has attempted to [resume] the isolate.
-  Future<Success> requirePermissionToResume(
-      {bool onPauseStart, bool onPauseReload, bool onPauseExit});
-
   /// The `resume` RPC is used to resume execution of a paused isolate.
   ///
   /// If the `step` parameter is not provided, the program will resume regular
@@ -1021,15 +968,6 @@
   Future<Success> resume(String isolateId,
       {/*StepOption*/ String step, int frameIndex});
 
-  /// The `setClientName` RPC is used to set a name to be associated with the
-  /// currently connected VM service client. If the `name` parameter is a
-  /// non-empty string, `name` will become the new name associated with the
-  /// client. If `name` is an empty string, the client's name will be reset to
-  /// its default name.
-  ///
-  /// See [Success].
-  Future<Success> setClientName(String name);
-
   /// The `setExceptionPauseMode` RPC is used to control if an isolate pauses
   /// when an exception is thrown.
   ///
@@ -1322,9 +1260,6 @@
             params['isolateId'],
           );
           break;
-        case 'getClientName':
-          response = await _serviceImplementation.getClientName();
-          break;
         case 'getCpuSamples':
           response = await _serviceImplementation.getCpuSamples(
             params['isolateId'],
@@ -1428,9 +1363,6 @@
         case 'getVMTimelineMicros':
           response = await _serviceImplementation.getVMTimelineMicros();
           break;
-        case 'getWebSocketTarget':
-          response = await _serviceImplementation.getWebSocketTarget();
-          break;
         case 'pause':
           response = await _serviceImplementation.pause(
             params['isolateId'],
@@ -1461,13 +1393,6 @@
             params['isolateId'],
           );
           break;
-        case 'requirePermissionToResume':
-          response = await _serviceImplementation.requirePermissionToResume(
-            onPauseStart: params['onPauseStart'],
-            onPauseReload: params['onPauseReload'],
-            onPauseExit: params['onPauseExit'],
-          );
-          break;
         case 'resume':
           response = await _serviceImplementation.resume(
             params['isolateId'],
@@ -1475,11 +1400,6 @@
             frameIndex: params['frameIndex'],
           );
           break;
-        case 'setClientName':
-          response = await _serviceImplementation.setClientName(
-            params['name'],
-          );
-          break;
         case 'setExceptionPauseMode':
           response = await _serviceImplementation.setExceptionPauseMode(
             params['isolateId'],
@@ -1792,9 +1712,6 @@
       _call('getClassList', {'isolateId': isolateId});
 
   @override
-  Future<ClientName> getClientName() => _call('getClientName');
-
-  @override
   Future<CpuSamples> getCpuSamples(
           String isolateId, int timeOriginMicros, int timeExtentMicros) =>
       _call('getCpuSamples', {
@@ -1910,9 +1827,6 @@
   Future<Timestamp> getVMTimelineMicros() => _call('getVMTimelineMicros');
 
   @override
-  Future<WebSocketTarget> getWebSocketTarget() => _call('getWebSocketTarget');
-
-  @override
   Future<Success> pause(String isolateId) =>
       _call('pause', {'isolateId': isolateId});
 
@@ -1950,15 +1864,6 @@
       _call('requestHeapSnapshot', {'isolateId': isolateId});
 
   @override
-  Future<Success> requirePermissionToResume(
-          {bool onPauseStart, bool onPauseReload, bool onPauseExit}) =>
-      _call('requirePermissionToResume', {
-        if (onPauseStart != null) 'onPauseStart': onPauseStart,
-        if (onPauseReload != null) 'onPauseReload': onPauseReload,
-        if (onPauseExit != null) 'onPauseExit': onPauseExit,
-      });
-
-  @override
   Future<Success> resume(String isolateId,
           {/*StepOption*/ String step, int frameIndex}) =>
       _call('resume', {
@@ -1968,10 +1873,6 @@
       });
 
   @override
-  Future<Success> setClientName(String name) =>
-      _call('setClientName', {'name': name});
-
-  @override
   Future<Success> setExceptionPauseMode(
           String isolateId, /*ExceptionPauseMode*/ String mode) =>
       _call('setExceptionPauseMode', {'isolateId': isolateId, 'mode': mode});
@@ -3074,35 +2975,6 @@
   String toString() => '[ClassList type: ${type}, classes: ${classes}]';
 }
 
-/// See [getClientName] and [setClientName].
-class ClientName extends Response {
-  static ClientName parse(Map<String, dynamic> json) =>
-      json == null ? null : ClientName._fromJson(json);
-
-  /// The name of the currently connected VM service client.
-  String name;
-
-  ClientName({
-    @required this.name,
-  });
-
-  ClientName._fromJson(Map<String, dynamic> json) : super._fromJson(json) {
-    name = json['name'];
-  }
-
-  @override
-  Map<String, dynamic> toJson() {
-    var json = <String, dynamic>{};
-    json['type'] = 'ClientName';
-    json.addAll({
-      'name': name,
-    });
-    return json;
-  }
-
-  String toString() => '[ClientName type: ${type}, name: ${name}]';
-}
-
 /// `CodeRef` is a reference to a `Code` object.
 class CodeRef extends ObjRef {
   static CodeRef parse(Map<String, dynamic> json) =>
@@ -7171,32 +7043,3 @@
 
   String toString() => '[VM]';
 }
-
-/// See [getWebSocketTarget]
-class WebSocketTarget extends Response {
-  static WebSocketTarget parse(Map<String, dynamic> json) =>
-      json == null ? null : WebSocketTarget._fromJson(json);
-
-  /// The web socket URI that should be used to connect to the service.
-  String uri;
-
-  WebSocketTarget({
-    @required this.uri,
-  });
-
-  WebSocketTarget._fromJson(Map<String, dynamic> json) : super._fromJson(json) {
-    uri = json['uri'];
-  }
-
-  @override
-  Map<String, dynamic> toJson() {
-    var json = <String, dynamic>{};
-    json['type'] = 'WebSocketTarget';
-    json.addAll({
-      'uri': uri,
-    });
-    return json;
-  }
-
-  String toString() => '[WebSocketTarget type: ${type}, uri: ${uri}]';
-}
diff --git a/pkg/vm_service/pubspec.yaml b/pkg/vm_service/pubspec.yaml
index 6c4cda5..fff9595 100644
--- a/pkg/vm_service/pubspec.yaml
+++ b/pkg/vm_service/pubspec.yaml
@@ -2,7 +2,7 @@
 description: >-
   A library to communicate with a service implementing the Dart VM
   service protocol.
-version: 4.2.0
+version: 5.0.0
 
 homepage: https://github.com/dart-lang/sdk/tree/master/pkg/vm_service
 
diff --git a/runtime/bin/main.cc b/runtime/bin/main.cc
index 000855d..661dfaf 100644
--- a/runtime/bin/main.cc
+++ b/runtime/bin/main.cc
@@ -544,15 +544,23 @@
   result = Dart_SetDeferredLoadHandler(Loader::DeferredLoadHandler);
   CHECK_RESULT(result);
 
+  int vm_service_server_port = INVALID_VM_SERVICE_SERVER_PORT;
+  if (Options::disable_dart_dev()) {
+    vm_service_server_port = Options::vm_service_server_port();
+  } else if (Options::vm_service_server_port() !=
+             INVALID_VM_SERVICE_SERVER_PORT) {
+    vm_service_server_port = 0;
+  }
+
   // Load embedder specific bits and return.
   if (!VmService::Setup(
-          Options::vm_service_server_ip(), Options::vm_service_server_port(),
-          Options::vm_service_dev_mode(), Options::vm_service_auth_disabled(),
+          Options::disable_dart_dev() ? Options::vm_service_server_ip()
+                                      : DEFAULT_VM_SERVICE_SERVER_IP,
+          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(),
-          // TODO(bkonyi): uncomment when DDS is re-enabled.
-          // See https://github.com/flutter/flutter/issues/62507
-          /*!Options::disable_dart_dev()*/ false)) {
+          !Options::disable_dart_dev())) {
     *error = Utils::StrDup(VmService::GetErrorMessage());
     return NULL;
   }
diff --git a/runtime/bin/main_options.cc b/runtime/bin/main_options.cc
index 8fa4f79..f1d8649 100644
--- a/runtime/bin/main_options.cc
+++ b/runtime/bin/main_options.cc
@@ -284,10 +284,6 @@
   return true;
 }
 
-static const char* DEFAULT_VM_SERVICE_SERVER_IP = "localhost";
-static const int DEFAULT_VM_SERVICE_SERVER_PORT = 8181;
-static const int INVALID_VM_SERVICE_SERVER_PORT = -1;
-
 const char* Options::vm_service_server_ip_ = DEFAULT_VM_SERVICE_SERVER_IP;
 int Options::vm_service_server_port_ = INVALID_VM_SERVICE_SERVER_PORT;
 bool Options::ProcessEnableVmServiceOption(const char* arg,
@@ -605,7 +601,15 @@
   }
 
   if (!Options::disable_dart_dev() && enable_vm_service_) {
-    dart_options->AddArgument("--launch-dds");
+    const char* dds_format_str = "--launch-dds=%s:%d";
+    size_t size = snprintf(nullptr, 0, dds_format_str, vm_service_server_ip(),
+                           vm_service_server_port());
+    // Make room for '\0'.
+    ++size;
+    char* dds_uri = new char[size];
+    snprintf(dds_uri, size, dds_format_str, vm_service_server_ip(),
+             vm_service_server_port());
+    dart_options->AddArgument(dds_uri);
   }
 
   // Verify consistency of arguments.
diff --git a/runtime/bin/main_options.h b/runtime/bin/main_options.h
index 76c50be..48553df 100644
--- a/runtime/bin/main_options.h
+++ b/runtime/bin/main_options.h
@@ -79,6 +79,10 @@
   kAppJIT,
 };
 
+static const char* DEFAULT_VM_SERVICE_SERVER_IP = "localhost";
+static const int DEFAULT_VM_SERVICE_SERVER_PORT = 8181;
+static const int INVALID_VM_SERVICE_SERVER_PORT = -1;
+
 class Options {
  public:
   static int ParseArguments(int argc,
diff --git a/runtime/observatory/tests/service/client_name_rpc_test.dart b/runtime/observatory/tests/service/client_name_rpc_test.dart
index 92aa4c9..8f81da0 100644
--- a/runtime/observatory/tests/service/client_name_rpc_test.dart
+++ b/runtime/observatory/tests/service/client_name_rpc_test.dart
@@ -66,4 +66,5 @@
 main(args) async => runVMTests(
       args,
       tests,
+      enableService: false,
     );
diff --git a/runtime/observatory/tests/service/client_resume_approvals_approve_then_disconnect_test.dart b/runtime/observatory/tests/service/client_resume_approvals_approve_then_disconnect_test.dart
index 439f170..28715a4 100644
--- a/runtime/observatory/tests/service/client_resume_approvals_approve_then_disconnect_test.dart
+++ b/runtime/observatory/tests/service/client_resume_approvals_approve_then_disconnect_test.dart
@@ -66,4 +66,5 @@
       testeeConcurrent: fooBar,
       pause_on_start: true,
       pause_on_exit: true,
+      enableService: false,
     );
diff --git a/runtime/observatory/tests/service/client_resume_approvals_disconnect_test.dart b/runtime/observatory/tests/service/client_resume_approvals_disconnect_test.dart
index d82317a..1ef5273 100644
--- a/runtime/observatory/tests/service/client_resume_approvals_disconnect_test.dart
+++ b/runtime/observatory/tests/service/client_resume_approvals_disconnect_test.dart
@@ -64,4 +64,5 @@
       testeeConcurrent: fooBar,
       pause_on_start: true,
       pause_on_exit: true,
+      enableService: false,
     );
diff --git a/runtime/observatory/tests/service/client_resume_approvals_identical_names_test.dart b/runtime/observatory/tests/service/client_resume_approvals_identical_names_test.dart
index dbcc14a..06df948 100644
--- a/runtime/observatory/tests/service/client_resume_approvals_identical_names_test.dart
+++ b/runtime/observatory/tests/service/client_resume_approvals_identical_names_test.dart
@@ -46,4 +46,5 @@
       testeeConcurrent: fooBar,
       pause_on_start: true,
       pause_on_exit: true,
+      enableService: false,
     );
diff --git a/runtime/observatory/tests/service/client_resume_approvals_multiple_names_test.dart b/runtime/observatory/tests/service/client_resume_approvals_multiple_names_test.dart
index 88d217d..780fe61 100644
--- a/runtime/observatory/tests/service/client_resume_approvals_multiple_names_test.dart
+++ b/runtime/observatory/tests/service/client_resume_approvals_multiple_names_test.dart
@@ -64,4 +64,5 @@
       testeeConcurrent: fooBar,
       pause_on_start: true,
       pause_on_exit: true,
+      enableService: false,
     );
diff --git a/runtime/observatory/tests/service/client_resume_approvals_name_change_test.dart b/runtime/observatory/tests/service/client_resume_approvals_name_change_test.dart
index 0e1374e..dac6f2b 100644
--- a/runtime/observatory/tests/service/client_resume_approvals_name_change_test.dart
+++ b/runtime/observatory/tests/service/client_resume_approvals_name_change_test.dart
@@ -57,4 +57,5 @@
       testeeConcurrent: fooBar,
       pause_on_start: true,
       pause_on_exit: true,
+      enableService: false,
     );
diff --git a/runtime/observatory/tests/service/client_resume_approvals_reload_test.dart b/runtime/observatory/tests/service/client_resume_approvals_reload_test.dart
index 08fdc10..fcd7e72 100644
--- a/runtime/observatory/tests/service/client_resume_approvals_reload_test.dart
+++ b/runtime/observatory/tests/service/client_resume_approvals_reload_test.dart
@@ -65,4 +65,5 @@
       hotReloadTest,
       testeeConcurrent: fooBar,
       pause_on_start: true,
+      enableService: false,
     );
diff --git a/runtime/observatory/tests/service/get_client_name_rpc_test.dart b/runtime/observatory/tests/service/get_client_name_rpc_test.dart
index 25b7828..e9f6ae3 100644
--- a/runtime/observatory/tests/service/get_client_name_rpc_test.dart
+++ b/runtime/observatory/tests/service/get_client_name_rpc_test.dart
@@ -41,4 +41,5 @@
       args,
       test,
       testeeBefore: fooBar,
+      enableService: false,
     );
diff --git a/runtime/observatory/tests/service/get_version_rpc_test.dart b/runtime/observatory/tests/service/get_version_rpc_test.dart
index 37c147a..7ee46a0 100644
--- a/runtime/observatory/tests/service/get_version_rpc_test.dart
+++ b/runtime/observatory/tests/service/get_version_rpc_test.dart
@@ -11,8 +11,8 @@
   (VM vm) async {
     var result = await vm.invokeRpcNoUpgrade('getVersion', {});
     expect(result['type'], equals('Version'));
-    expect(result['major'], equals(3));
-    expect(result['minor'], equals(38));
+    expect(result['major'], equals(4));
+    expect(result['minor'], equals(0));
     expect(result['_privateMajor'], equals(0));
     expect(result['_privateMinor'], equals(0));
   },
diff --git a/runtime/observatory/tests/service/vm_service_dds_test.dart b/runtime/observatory/tests/service/vm_service_dds_test.dart
index c17cc6f..d2f2922 100644
--- a/runtime/observatory/tests/service/vm_service_dds_test.dart
+++ b/runtime/observatory/tests/service/vm_service_dds_test.dart
@@ -12,7 +12,7 @@
   (VM vm, DartDevelopmentService dds) async {
     final client = WebSocketVM(
       WebSocketVMTarget(
-        dds.remoteVmServiceWsUri.toString(),
+        dds.wsUri.toString(),
       ),
     );
     final result = await client.invokeRpcNoUpgrade('getSupportedProtocols', {});
diff --git a/runtime/vm/service.h b/runtime/vm/service.h
index 896c695..b4f7acd 100644
--- a/runtime/vm/service.h
+++ b/runtime/vm/service.h
@@ -14,8 +14,8 @@
 
 namespace dart {
 
-#define SERVICE_PROTOCOL_MAJOR_VERSION 3
-#define SERVICE_PROTOCOL_MINOR_VERSION 38
+#define SERVICE_PROTOCOL_MAJOR_VERSION 4
+#define SERVICE_PROTOCOL_MINOR_VERSION 0
 
 class Array;
 class EmbedderServiceHandler;
diff --git a/runtime/vm/service/service.md b/runtime/vm/service/service.md
index 4ba84da..5eeb755 100644
--- a/runtime/vm/service/service.md
+++ b/runtime/vm/service/service.md
@@ -1,8 +1,8 @@
-# Dart VM Service Protocol 3.38
+# Dart VM Service Protocol 4.0 
 
 > Please post feedback to the [observatory-discuss group][discuss-list]
 
-This document describes of _version 3.38_ of the Dart VM Service Protocol. This
+This document describes of _version 4.0_ of the Dart VM Service Protocol. This
 protocol is used to communicate with a running Dart Virtual Machine.
 
 To use the Service Protocol, start the VM with the *--observe* flag.
@@ -39,7 +39,6 @@
   - [evaluate](#evaluate)
   - [evaluateInFrame](#evaluateinframe)
   - [getAllocationProfile](#getallocationprofile)
-  - [getClientName](#getclientname)
   - [getCpuSamples](#getcpusamples)
   - [getFlagList](#getflaglist)
   - [getInstances](#getinstances)
@@ -59,16 +58,13 @@
   - [getVMTimeline](#getvmtimeline)
   - [getVMTimelineFlags](#getvmtimelineflags)
   - [getVMTimelineMicros](#getvmtimelinemicros)
-  - [getWebSocketTarget](#getwebsockettarget)
   - [invoke](#invoke)
   - [pause](#pause)
   - [kill](#kill)
   - [registerService](#registerService)
   - [reloadSources](#reloadsources)
   - [removeBreakpoint](#removebreakpoint)
-  - [requirePermissionToResume](#requirepermissiontoresume)
   - [resume](#resume)
-  - [setClientName](#setclientname)
   - [setExceptionPauseMode](#setexceptionpausemode)
   - [setFlag](#setflag)
   - [setLibraryDebuggable](#setlibrarydebuggable)
@@ -743,21 +739,6 @@
 
 See [ClassList](#classlist).
 
-### getClientName
-
-_**Note**: This method is deprecated and will be removed in v4.0 of the protocol.
-An equivalent can be found in the Dart Development Service (DDS) protocol._
-
-```
-ClientName getClientName()
-```
-
-The _getClientName_ RPC is used to retrieve the name associated with the currently
-connected VM service client. If no name was previously set through the
-[setClientName](#setclientname) RPC, a default name will be returned.
-
-See [ClientName](#clientname).
-
 ### getCpuSamples
 
 ```
@@ -1144,17 +1125,6 @@
 
 See [Timestamp](#timestamp) and [getVMTimeline](#getvmtimeline).
 
-### getWebSocketTarget
-
-```
-WebSocketTarget getWebSocketTarget()
-```
-
-The _getWebSocketTarget_ RPC returns the web socket URI that should be used by VM service clients
-with WebSocket implementations that do not follow redirects (e.g., `dart:html`'s [WebSocket](https://api.dart.dev/dart-html/WebSocket-class.html)).
-
-See [WebSocketTarget](#websockettarget).
-
 ### pause
 
 ```
@@ -1260,44 +1230,6 @@
 If _isolateId_ refers to an isolate which has exited, then the
 _Collected_ [Sentinel](#sentinel) is returned.
 
-### requirePermissionToResume
-
-_**Note**: This method is deprecated and will be removed in v4.0 of the protocol.
-An equivalent can be found in the Dart Development Service (DDS) protocol._
-
-```
-Success requirePermissionToResume(bool onPauseStart [optional],
-                                  bool onPauseReload[optional],
-                                  bool onPauseExit [optional])
-```
-
-The _requirePermissionToResume_ RPC is used to change the pause/resume behavior
-of isolates by providing a way for the VM service to wait for approval to resume
-from some set of clients. This is useful for clients which want to perform some
-operation on an isolate after a pause without it being resumed by another client.
-
-If the _onPauseStart_ parameter is `true`, isolates will not resume after pausing
-on start until the client sends a `resume` request and all other clients which
-need to provide resume approval for this pause type have done so.
-
-If the _onPauseReload_ parameter is `true`, isolates will not resume after pausing
-after a reload until the client sends a `resume` request and all other clients
-which need to provide resume approval for this pause type have done so.
-
-If the _onPauseExit_ parameter is `true`, isolates will not resume after pausing
-on exit until the client sends a `resume` request and all other clients which
-need to provide resume approval for this pause type have done so.
-
-**Important Notes:**
-
-- All clients with the same client name share resume permissions. Only a
-  single client of a given name is required to provide resume approval.
-- When a client requiring approval disconnects from the service, a paused
-  isolate may resume if all other clients requiring resume approval have
-  already given approval. In the case that no other client requires resume
-  approval for the current pause event, the isolate will be resumed if at
-  least one other client has attempted to [resume](#resume) the isolate.
-
 ### resume
 
 ```
@@ -1332,22 +1264,6 @@
 
 See [Success](#success), [StepOption](#StepOption).
 
-### setClientName
-
-_**Note**: This method is deprecated and will be removed in v4.0 of the protocol.
-An equivalent can be found in the Dart Development Service (DDS) protocol._
-
-```
-Success setClientName(string name)
-```
-
-The _setClientName_ RPC is used to set a name to be associated with the currently
-connected VM service client. If the _name_ parameter is a non-empty string, _name_
-will become the new name associated with the client. If _name_ is an empty string,
-the client's name will be reset to its default name.
-
-See [Success](#success).
-
 ### setExceptionPauseMode
 
 ```
@@ -1783,20 +1699,6 @@
 }
 ```
 
-### ClientName
-
-_**Note**: This class is deprecated and will be removed in v4.0 of the protocol.
-An equivalent can be found in the Dart Development Service (DDS) protocol._
-
-```
-class ClientName extends Response {
-  // The name of the currently connected VM service client.
-  string name;
-}
-```
-
-See [getClientName](#getclientname) and [setClientName](#setclientname).
-
 ### Code
 
 ```
@@ -3887,17 +3789,6 @@
 }
 ```
 
-### WebSocketTarget
-
-```
-class WebSocketTarget extends Response {
-  // The web socket URI that should be used to connect to the service.
-  string uri;
-}
-```
-
-See [getWebSocketTarget](#getwebsockettarget)
-
 ## Revision History
 
 version | comments
@@ -3935,15 +3826,14 @@
 3.28 | TODO(aam): document changes from 3.28
 3.29 | Add `getClientName`, `setClientName`, `requireResumeApproval`
 3.30 | Updated return types of RPCs which require an `isolateId` to allow for `Sentinel` results if the target isolate has shutdown.
-3.31 | Added single client mode, which allows for the Dart Development Service (DDS) to become the sole client of
-the VM service.
+3.31 | Added single client mode, which allows for the Dart Development Service (DDS) to become the sole client of the VM service.
 3.32 | Added `getClassList` RPC and `ClassList` object.
 3.33 | Added deprecation notice for `getClientName`, `setClientName`, `requireResumeApproval`, and `ClientName`. These RPCs are moving to the DDS protocol and will be removed in v4.0 of the VM service protocol.
 3.34 | Added `TimelineStreamSubscriptionsUpdate` event which is sent when `setVMTimelineFlags` is invoked.
 3.35 | Added `getSupportedProtocols` RPC and `ProtocolList`, `Protocol` objects.
 3.36 | Added `getProcessMemoryUsage` RPC and `ProcessMemoryUsage` and `ProcessMemoryItem` objects.
 3.37 | Added `getWebSocketTarget` RPC and `WebSocketTarget` object.
-3.38 | Added `isSystemIsolate` property to `@Isolate` and `Isolate`, `isSystemIsolateGroup` property to `@IsolateGroup` and `IsolateGroup`,
-and properties `systemIsolates` and `systemIsolateGroups` to `VM`.
+3.38 | Added `isSystemIsolate` property to `@Isolate` and `Isolate`, `isSystemIsolateGroup` property to `@IsolateGroup` and `IsolateGroup`, and properties `systemIsolates` and `systemIsolateGroups` to `VM`.
+4.0 | Removed the following deprecated RPCs and objects: `getClientName`, `getWebSocketTarget`, `setClientName`, `requireResumeApproval`, `ClientName`, and `WebSocketTarget`.
 
 [discuss-list]: https://groups.google.com/a/dartlang.org/forum/#!forum/observatory-discuss
diff --git a/runtime/vm/service_event.cc b/runtime/vm/service_event.cc
index 5b90440..18e06f9 100644
--- a/runtime/vm/service_event.cc
+++ b/runtime/vm/service_event.cc
@@ -184,9 +184,6 @@
   if (kind() == kVMFlagUpdate) {
     jsobj.AddProperty("flag", flag_name());
     // For backwards compatibility, "new_value" is also provided.
-    // TODO(bkonyi): remove when service protocol major version is incremented.
-    ASSERT(SERVICE_PROTOCOL_MAJOR_VERSION == 3);
-    jsobj.AddProperty("new_value", flag_new_value());
     jsobj.AddProperty("newValue", flag_new_value());
   }
   if (kind() == kIsolateReload) {
diff --git a/sdk/lib/_internal/vm/bin/vmservice_io.dart b/sdk/lib/_internal/vm/bin/vmservice_io.dart
index 329e505..d1f8a5d 100644
--- a/sdk/lib/_internal/vm/bin/vmservice_io.dart
+++ b/sdk/lib/_internal/vm/bin/vmservice_io.dart
@@ -81,11 +81,20 @@
 }
 
 Future<void> ddsConnectedCallback() async {
+  final serviceAddress = server!.serverAddress.toString();
+  _notifyServerState(serviceAddress);
+  onServerAddressChange(serviceAddress);
   if (_waitForDdsToAdvertiseService) {
     await server!.outputConnectionInformation();
   }
 }
 
+Future<void> ddsDisconnectedCallback() async {
+  final serviceAddress = server!.serverAddress.toString();
+  _notifyServerState(serviceAddress);
+  onServerAddressChange(serviceAddress);
+}
+
 Future<Uri> createTempDirCallback(String base) async {
   final temp = await Directory.systemTemp.createTemp(base);
   // Underneath the temporary directory, create a directory with the
@@ -257,6 +266,7 @@
   VMServiceEmbedderHooks.cleanup = cleanupCallback;
   VMServiceEmbedderHooks.createTempDir = createTempDirCallback;
   VMServiceEmbedderHooks.ddsConnected = ddsConnectedCallback;
+  VMServiceEmbedderHooks.ddsDisconnected = ddsDisconnectedCallback;
   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 f084f29..383b19b 100644
--- a/sdk/lib/_internal/vm/bin/vmservice_server.dart
+++ b/sdk/lib/_internal/vm/bin/vmservice_server.dart
@@ -158,9 +158,14 @@
 
   /// Returns the server address including the auth token.
   Uri? get serverAddress {
-    if (!running) {
+    if (!running || _service.isExiting) {
       return null;
     }
+    // If DDS is connected it should be treated as the "true" VM service and be
+    // advertised as such.
+    if (_service.ddsUri != null) {
+      return _service.ddsUri;
+    }
     final server = _server!;
     final ip = server.address.address;
     final port = server.port;
@@ -364,32 +369,11 @@
           WebSocketClient(webSocket, _service);
         });
       } else {
-        // Forward the websocket connection request to DDS.
-        // The Javascript WebSocket implementation doesn't like websocket
-        // connection requests being redirected. Instead of redirecting, we'll
-        // just forward the connection manually if 'implicit-redirect' is
-        // provided as a protocol.
-        if (subprotocols != null) {
-          if (subprotocols.contains('implicit-redirect')) {
-            WebSocketTransformer.upgrade(request,
-                    protocolSelector: (_) => 'implicit-redirect',
-                    compression: CompressionOptions.compressionOff)
-                .then((WebSocket webSocket) async {
-              final ddsWs = await WebSocket.connect(
-                  _service.ddsUri!.replace(scheme: 'ws').toString());
-              ddsWs.addStream(webSocket);
-              webSocket.addStream(ddsWs);
-              webSocket.done.then((_) {
-                ddsWs.close();
-              });
-              ddsWs.done.then((_) {
-                webSocket.close();
-              });
-            });
-            return;
-          }
-        }
-        request.response.redirect(_service.ddsUri!);
+        request.response.statusCode = HttpStatus.forbidden;
+        request.response.write('Cannot connect directly to the VM service as '
+            'a Dart Development Service (DDS) instance has taken control and '
+            'can be found at ${_service.ddsUri}.');
+        request.response.close();
       }
       return;
     }
diff --git a/sdk/lib/vmservice/running_isolate.dart b/sdk/lib/vmservice/running_isolate.dart
index 029c6aa..8a6dd36 100644
--- a/sdk/lib/vmservice/running_isolate.dart
+++ b/sdk/lib/vmservice/running_isolate.dart
@@ -8,122 +8,13 @@
   final int portId;
   final SendPort sendPort;
   final String name;
-  final Set<String> _resumeApprovalsByName = {};
 
   RunningIsolate(this.portId, this.sendPort, this.name);
 
   String get serviceId => 'isolates/$portId';
 
-  static const kInvalidPauseEvent = -1;
-  static const kPauseOnStartMask = 1 << 0;
-  static const kPauseOnReloadMask = 1 << 1;
-  static const kPauseOnExitMask = 1 << 2;
-  static const kDefaultResumePermissionMask =
-      kPauseOnStartMask | kPauseOnReloadMask | kPauseOnExitMask;
-
-  /// Resumes the isolate if all clients which need to approve a resume have
-  /// done so. Called when the last client of a given name disconnects or
-  /// changes name to ensure we don't deadlock waiting for approval to resume
-  /// from a disconnected client.
-  Future<void> maybeResumeAfterClientChange(
-      VMService service, String disconnectedClientName) async {
-    // Remove approvals from the disconnected client.
-    _resumeApprovalsByName.remove(disconnectedClientName);
-
-    // If we've received approval to resume from all clients who care, clear
-    // approval state and resume.
-    var pauseType;
-    try {
-      pauseType = await _isolatePauseType(service, portId.toString());
-    } catch (_errorResponse) {
-      // ignore errors when attempting to retrieve isolate pause type
-      return;
-    }
-    if (pauseType != kInvalidPauseEvent &&
-        _shouldResume(service, null, pauseType)) {
-      _resumeApprovalsByName.clear();
-      await Message.forMethod('resume')
-        ..params.addAll({
-          'isolateId': portId,
-        })
-        ..sendToIsolate(sendPort);
-    }
-  }
-
-  bool _shouldResume(VMService service, Client? client, int pauseType) {
-    if (client != null) {
-      // Mark the approval by the client.
-      _resumeApprovalsByName.add(client.name);
-    }
-    final requiredClientApprovals = <String>{};
-    final permissions = service.clientResumePermissions;
-
-    // Determine which clients require approval for this pause type.
-    permissions.forEach((name, clientNamePermissions) {
-      if (clientNamePermissions.permissionsMask & pauseType != 0) {
-        requiredClientApprovals.add(name);
-      }
-    });
-
-    // We require at least a single client to resume, even if that client
-    // doesn't require resume approval.
-    if (_resumeApprovalsByName.isEmpty) {
-      return false;
-    }
-
-    // If all the required approvals are present, we should resume.
-    return _resumeApprovalsByName.containsAll(requiredClientApprovals);
-  }
-
-  Future<int> _isolatePauseType(VMService service, String isolateId) async {
-    final getIsolateMessage = Message.forMethod('getIsolate')
-      ..params.addAll({
-        'isolateId': isolateId,
-      });
-    final Response result = await routeRequest(service, getIsolateMessage);
-    final resultJson = result.decodeJson();
-    if (resultJson['result'] == null ||
-        resultJson['result']['pauseEvent'] == null) {
-      // Failed to send getIsolate message(due to isolate being de-registered
-      // for example).
-      throw result;
-    }
-    final pauseEvent = resultJson['result']['pauseEvent'];
-    const pauseEvents = <String, int>{
-      'PauseStart': kPauseOnStartMask,
-      'PausePostRequest': kPauseOnReloadMask,
-      'PauseExit': kPauseOnExitMask,
-    };
-    final kind = pauseEvent['kind'];
-    return pauseEvents[kind] ?? kInvalidPauseEvent;
-  }
-
-  Future<Response> _routeResumeRequest(
-      VMService service, Message message) async {
-    // If we've received approval to resume from all clients who care, clear
-    // approval state and resume.
-    var pauseType;
-    try {
-      pauseType = await _isolatePauseType(service, message.params['isolateId']);
-    } on Response catch (errorResponse) {
-      return errorResponse;
-    }
-    if (pauseType == kInvalidPauseEvent ||
-        _shouldResume(service, message.client, pauseType)) {
-      _resumeApprovalsByName.clear();
-      return message.sendToIsolate(sendPort);
-    }
-
-    // We're still awaiting some approvals. Simply return success, but don't
-    // resume yet.
-    return Response(ResponsePayloadKind.String, encodeSuccess(message));
-  }
-
   @override
   Future<Response> routeRequest(VMService service, Message message) {
-    if (message.method == 'resume') {
-      return _routeResumeRequest(service, message);
-    }
     // Send message to isolate.
     return message.sendToIsolate(sendPort);
   }
diff --git a/sdk/lib/vmservice/vmservice.dart b/sdk/lib/vmservice/vmservice.dart
index 1581e70..e0ccfaa 100644
--- a/sdk/lib/vmservice/vmservice.dart
+++ b/sdk/lib/vmservice/vmservice.dart
@@ -142,6 +142,9 @@
 /// Called when DDS has connected.
 typedef Future<void> DdsConnectedCallback();
 
+/// Called when DDS has disconnected.
+typedef Future<void> DdsDisconnectedCallback();
+
 /// Called when the service is exiting.
 typedef Future CleanupCallback();
 
@@ -182,6 +185,7 @@
   static ServerStartCallback? serverStart;
   static ServerStopCallback? serverStop;
   static DdsConnectedCallback? ddsConnected;
+  static DdsDisconnectedCallback? ddsDisconnected;
   static CleanupCallback? cleanup;
   static CreateTempDirCallback? createTempDir;
   static DeleteDirCallback? deleteDir;
@@ -224,8 +228,6 @@
 
   final devfs = DevFS();
 
-  Uri? vmServiceUri;
-
   Uri? get ddsUri => _ddsUri;
   Uri? _ddsUri;
 
@@ -254,117 +256,6 @@
     return encodeSuccess(message);
   }
 
-  void _clearClientName(Client client) {
-    final name = client.name;
-    client.name = null;
-    final clientsForName = clientResumePermissions[name];
-    if (clientsForName != null) {
-      clientsForName.clients.remove(client);
-      // If this was the last client with a given name, cleanup resume
-      // permissions.
-      if (clientsForName.clients.isEmpty) {
-        clientResumePermissions.remove(name);
-
-        // Check to see if we need to resume any isolates now that the last
-        // client of a given name has disconnected or changed names.
-        //
-        // An isolate will be resumed in this situation if:
-        //
-        // 1) This client required resume approvals for the current pause event
-        // associated with the isolate and all other required resume approvals
-        // have been provided by other clients.
-        //
-        // OR
-        //
-        // 2) This client required resume approvals for the current pause event
-        // associated with the isolate, no other clients require resume approvals
-        // for the current pause event, and at least one client has issued a resume
-        // request.
-        runningIsolates.isolates.forEach((_, isolate) async =>
-            await isolate.maybeResumeAfterClientChange(this, name));
-      }
-    }
-  }
-
-  /// Sets the name associated with a [Client].
-  ///
-  /// If any resume approvals were set for this client previously they will
-  /// need to be reset after a name change.
-  String _setClientName(Message message) {
-    final client = message.client!;
-    if (!message.params.containsKey('name')) {
-      return encodeRpcError(message, kInvalidParams,
-          details: "setClientName: missing required parameter 'name'");
-    }
-    final name = message.params['name'];
-    if (name is! String) {
-      return encodeRpcError(message, kInvalidParams,
-          details: "setClientName: invalid 'name' parameter: $name");
-    }
-    _setClientNameHelper(client, name);
-    return encodeSuccess(message);
-  }
-
-  void _setClientNameHelper(Client client, String name) {
-    _clearClientName(client);
-    name = name.isEmpty ? client.defaultClientName : name;
-    client.name = name;
-    clientResumePermissions.putIfAbsent(
-        client.name, () => _ClientResumePermissions());
-    clientResumePermissions[name]!.clients.add(client);
-  }
-
-  String _getClientName(Message message) => encodeResult(message, {
-        'type': 'ClientName',
-        'name': message.client!.name,
-      });
-
-  String _requirePermissionToResume(Message message) {
-    bool parsePermission(String argName) {
-      final arg = message.params[argName];
-      if (arg == null) {
-        return false;
-      }
-      if (arg is! bool) {
-        throw encodeRpcError(message, kInvalidParams,
-            details: "requirePermissionToResume: invalid '$argName': $arg");
-      }
-      return arg;
-    }
-
-    final client = message.client!;
-    int pauseTypeMask = 0;
-    try {
-      if (parsePermission('onPauseStart')) {
-        pauseTypeMask |= RunningIsolate.kPauseOnStartMask;
-      }
-      if (parsePermission('onPauseReload')) {
-        pauseTypeMask |= RunningIsolate.kPauseOnReloadMask;
-      }
-      if (parsePermission('onPauseExit')) {
-        pauseTypeMask |= RunningIsolate.kPauseOnExitMask;
-      }
-    } on dynamic catch (rpcError) {
-      return rpcError;
-    }
-
-    clientResumePermissions[client.name]!.permissionsMask = pauseTypeMask;
-    return encodeSuccess(message);
-  }
-
-  String _getWebSocketTarget(Message message) {
-    Uri uri = ((_ddsUri != null) ? _ddsUri : vmServiceUri)!;
-    uri = uri.replace(scheme: 'ws', pathSegments: [
-      // Strip empty path segment which causes an extra / to be inserted.
-      ...uri.pathSegments.where((e) => e.isNotEmpty),
-      'ws',
-    ]);
-    return encodeResult(message, {
-      'type': 'WebSocketTarget',
-      'uri': uri.toString(),
-    });
-  }
-
   void _addClient(Client client) {
     assert(client.streams.isEmpty);
     assert(client.services.isEmpty);
@@ -380,9 +271,6 @@
       }
     }
 
-    // Clean up client approvals state.
-    _clearClientName(client);
-
     for (final service in client.services.keys) {
       _eventMessageHandler(
           'Service',
@@ -411,8 +299,9 @@
       final acceptNewWebSocketConnections =
           VMServiceEmbedderHooks.acceptNewWebSocketConnections;
       if (_ddsUri != null && acceptNewWebSocketConnections != null) {
-        acceptNewWebSocketConnections(true);
         _ddsUri = null;
+        VMServiceEmbedderHooks.ddsDisconnected!();
+        acceptNewWebSocketConnections(true);
       }
     }
   }
@@ -581,11 +470,9 @@
   }
 
   Client? _findFirstClientThatHandlesService(String service) {
-    if (clients != null) {
-      for (Client c in clients) {
-        if (c.services.containsKey(service)) {
-          return c;
-        }
+    for (Client c in clients) {
+      if (c.services.containsKey(service)) {
+        return c;
       }
     }
     return null;
@@ -707,22 +594,20 @@
     final namespace = _getNamespace(message.method!);
     final method = _getMethod(message.method!);
     final client = clients[namespace];
-    if (client != null) {
-      if (client.services.containsKey(method)) {
-        final id = _serviceRequests.newId();
-        final oldId = message.serial;
-        final completer = Completer<String>();
-        client.serviceHandles[id] = (Message? m) {
-          if (m != null) {
-            completer.complete(json.encode(m.forwardToJson({'id': oldId})));
-          } else {
-            completer.complete(encodeRpcError(message, kServiceDisappeared));
-          }
-        };
-        client.post(
-            Response.json(message.forwardToJson({'id': id, 'method': method})));
-        return completer.future;
-      }
+    if (client.services.containsKey(method)) {
+      final id = _serviceRequests.newId();
+      final oldId = message.serial;
+      final completer = Completer<String>();
+      client.serviceHandles[id] = (Message? m) {
+        if (m != null) {
+          completer.complete(json.encode(m.forwardToJson({'id': oldId})));
+        } else {
+          completer.complete(encodeRpcError(message, kServiceDisappeared));
+        }
+      };
+      client.post(
+          Response.json(message.forwardToJson({'id': id, 'method': method})));
+      return completer.future;
     }
     return encodeRpcError(message, kMethodNotFound,
         details: 'Unknown service: ${message.method}');
@@ -774,21 +659,9 @@
       if (message.method == 'registerService') {
         return await _registerService(message);
       }
-      if (message.method == 'setClientName') {
-        return _setClientName(message);
-      }
-      if (message.method == 'getClientName') {
-        return _getClientName(message);
-      }
-      if (message.method == 'requirePermissionToResume') {
-        return _requirePermissionToResume(message);
-      }
       if (message.method == 'getSupportedProtocols') {
         return await _getSupportedProtocols(message);
       }
-      if (message.method == 'getWebSocketTarget') {
-        return _getWebSocketTarget(message);
-      }
       if (devfs.shouldHandleMessage(message)) {
         return await devfs.handleMessage(message);
       }
@@ -834,7 +707,6 @@
 
 /// Notify the VM that the server's address has changed.
 void onServerAddressChange(String? address) {
-  VMService().vmServiceUri = (address != null) ? Uri.parse(address) : null;
   _onServerAddressChange(address);
 }