Version 2.17.0-272.0.dev

Merge commit '1af37bd92a9b08ac2d88d26c55feb1dd43382416' into 'dev'
diff --git a/pkg/_fe_analyzer_shared/lib/src/macros/compiler/request_channel.dart b/pkg/_fe_analyzer_shared/lib/src/macros/compiler/request_channel.dart
new file mode 100644
index 0000000..32cdd78
--- /dev/null
+++ b/pkg/_fe_analyzer_shared/lib/src/macros/compiler/request_channel.dart
@@ -0,0 +1,148 @@
+// Copyright (c) 2022, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+import 'dart:io';
+import 'dart:typed_data';
+
+import 'package:_fe_analyzer_shared/src/macros/executor/message_grouper.dart';
+import 'package:_fe_analyzer_shared/src/macros/executor/serialization.dart';
+
+/// An instance of this class is thrown when remotely executed code throws
+/// an exception.
+class RemoteException {
+  final String message;
+  final String stackTrace;
+
+  RemoteException({
+    required this.message,
+    required this.stackTrace,
+  });
+}
+
+/// Channel for exchanging requests and responses over [Socket].
+class RequestChannel {
+  final Socket _socket;
+  final Map<int, Completer<Object?>> _requests = {};
+  final Map<String, Future<Object?> Function(Object?)> _requestHandlers = {};
+
+  int _nextRequestId = 0;
+
+  RequestChannel(this._socket) {
+    final MessageGrouper messageGrouper = new MessageGrouper(_socket);
+    messageGrouper.messageStream.map((bytes) {
+      final ByteDataDeserializer deserializer = new ByteDataDeserializer(
+        new ByteData.sublistView(bytes),
+      );
+      return deserializer.expectAny();
+    }).listen(_processMessage, cancelOnError: true, onDone: () {
+      _socket.destroy();
+    });
+  }
+
+  /// Registers the [handler] for the [method].
+  void add(String method, Future<Object?> Function(Object?) handler) {
+    _requestHandlers[method] = handler;
+  }
+
+  /// Sends a request with the given [method] and [argument], when it is
+  /// handled on the other side by the handler registered with [add], the
+  /// result is returned from the [Future].
+  ///
+  /// If an exception happens on the other side, the returned future completes
+  /// with a [RemoteException].
+  ///
+  /// The other side may do callbacks.
+  Future<T> sendRequest<T>(String method, Object? argument) async {
+    final int requestId = _nextRequestId++;
+    final Completer<Object?> completer = new Completer<Object?>();
+    _requests[requestId] = completer;
+    _writeObject({
+      'requestId': requestId,
+      'method': method,
+      'argument': argument,
+    });
+
+    return await completer.future as T;
+  }
+
+  void _processMessage(Object? message) {
+    if (message is Map<Object?, Object?>) {
+      final Object? requestId = message['requestId'];
+      if (requestId != null) {
+        final Object? method = message['method'];
+        final Object? argument = message['argument'];
+        if (method != null) {
+          final Future<Object?> Function(Object?)? handler =
+              _requestHandlers[method];
+          if (handler != null) {
+            handler(argument).then((result) {
+              _writeObject({
+                'responseId': requestId,
+                'result': result,
+              });
+            }, onError: (e, stackTrace) {
+              _writeObject({
+                'responseId': requestId,
+                'exception': {
+                  'message': '$e',
+                  'stackTrace': '$stackTrace',
+                },
+              });
+            });
+          } else {
+            _writeObject({
+              'responseId': requestId,
+              'exception': {
+                'message': 'No handler for: $method',
+                'stackTrace': '${StackTrace.current}',
+              },
+            });
+          }
+          return;
+        }
+      }
+
+      final Object? responseId = message['responseId'];
+      if (responseId != null) {
+        final Completer<Object?>? completer = _requests.remove(responseId);
+        if (completer == null) {
+          return;
+        }
+
+        final Object? exception = message['exception'];
+        if (exception is Map) {
+          completer.completeError(
+            new RemoteException(
+              message: exception['message'] as String,
+              stackTrace: exception['stackTrace'] as String,
+            ),
+          );
+          return;
+        }
+
+        final Object? result = message['result'];
+        completer.complete(result);
+        return;
+      }
+    }
+
+    throw new StateError('Unexpected message: $message');
+  }
+
+  /// Sends [bytes] for [MessageGrouper].
+  void _writeBytes(List<int> bytes) {
+    final ByteData lengthByteData = new ByteData(4)..setUint32(0, bytes.length);
+    _socket.add(lengthByteData.buffer.asUint8List());
+    _socket.add(bytes);
+    _socket.flush();
+  }
+
+  /// Serializes and sends the [object].
+  void _writeObject(Object object) {
+    final ByteDataSerializer serializer = new ByteDataSerializer();
+    serializer.addAny(object);
+    _writeBytes(serializer.result);
+  }
+}
diff --git a/pkg/_fe_analyzer_shared/lib/src/macros/executor/serialization.dart b/pkg/_fe_analyzer_shared/lib/src/macros/executor/serialization.dart
index 50d98fd..0aece1c 100644
--- a/pkg/_fe_analyzer_shared/lib/src/macros/executor/serialization.dart
+++ b/pkg/_fe_analyzer_shared/lib/src/macros/executor/serialization.dart
@@ -278,7 +278,8 @@
   @override
   void addInt(int value) {
     if (value >= 0x0) {
-      if (value + DataKind.values.length <= 0xff) {
+      assert(DataKind.values.length < 0xff);
+      if (value <= 0xff - DataKind.values.length) {
         _builder..addByte(value + DataKind.values.length);
       } else if (value <= 0xff) {
         _builder
@@ -380,10 +381,52 @@
   }
 
   @override
-  void endList() => _builder.addByte(DataKind.endList.index);
+  void startList() => _builder.addByte(DataKind.startList.index);
 
   @override
-  void startList() => _builder.addByte(DataKind.startList.index);
+  void endList() => _builder.addByte(DataKind.endList.index);
+
+  /// Used to signal the start of an arbitrary length list of map entries.
+  void startMap() => _builder.addByte(DataKind.startMap.index);
+
+  /// Used to signal the end of an arbitrary length list of map entries.
+  void endMap() => _builder.addByte(DataKind.endMap.index);
+
+  /// Serializes a [Uint8List].
+  void addUint8List(Uint8List value) {
+    _builder.addByte(DataKind.uint8List.index);
+    addInt(value.length);
+    _builder.add(value);
+  }
+
+  /// Serializes an object with arbitrary structure. It supports `bool`,
+  /// `int`, `String`, `null`, `Uint8List`, `List`, `Map`.
+  void addAny(Object? value) {
+    if (value == null) {
+      addNull();
+    } else if (value is bool) {
+      addBool(value);
+    } else if (value is int) {
+      addInt(value);
+    } else if (value is String) {
+      addString(value);
+    } else if (value is Uint8List) {
+      addUint8List(value);
+    } else if (value is List) {
+      startList();
+      value.forEach(addAny);
+      endList();
+    } else if (value is Map) {
+      startMap();
+      for (MapEntry<Object?, Object?> entry in value.entries) {
+        addAny(entry.key);
+        addAny(entry.value);
+      }
+      endMap();
+    } else {
+      throw new ArgumentError('(${value.runtimeType}) $value');
+    }
+  }
 
   @override
   Uint8List get result => _builder.takeBytes();
@@ -494,6 +537,32 @@
     _byteOffsetIncrement = 1;
   }
 
+  /// Asserts that the current item is the start of a map.
+  ///
+  /// An example for how to read from a map is as follows:
+  ///
+  /// I know it's a map of ints to strings.
+  ///
+  /// ```
+  ///   var result = <int, String>[];
+  ///   deserializer.expectMap();
+  ///   while (deserializer.moveNext()) {
+  ///     var key = deserializer.expectInt();
+  ///     deserializer.next();
+  ///     var value = deserializer.expectString();
+  ///     result[key] = value;
+  ///   }
+  ///   // We have already called `moveNext` to move past the map.
+  ///   deserializer.expectBool();
+  /// ```
+  void expectMap() {
+    DataKind kind = _readKind();
+    if (kind != DataKind.startMap) {
+      throw new StateError('Expected the start to a map but found a $kind');
+    }
+    _byteOffsetIncrement = 1;
+  }
+
   @override
   String expectString() {
     DataKind kind = _readKind();
@@ -514,6 +583,75 @@
     }
   }
 
+  /// Reads the current value as [Uint8List].
+  Uint8List expectUint8List() {
+    _byteOffsetIncrement = 1;
+    moveNext();
+    int length = expectInt();
+    int offset = _byteOffset + _byteOffsetIncrement!;
+    _byteOffsetIncrement = _byteOffsetIncrement! + length;
+    return _bytes.buffer.asUint8List(offset, length);
+  }
+
+  /// Reads the current value as an object of arbitrary structure.
+  Object? expectAny() {
+    const Set<DataKind> boolKinds = {
+      DataKind.boolFalse,
+      DataKind.boolTrue,
+    };
+
+    const Set<DataKind> intKinds = {
+      DataKind.directEncodedUint8,
+      DataKind.int8,
+      DataKind.int16,
+      DataKind.int32,
+      DataKind.int64,
+      DataKind.uint8,
+      DataKind.uint16,
+      DataKind.uint32,
+      DataKind.uint64,
+    };
+
+    const Set<DataKind> stringKinds = {
+      DataKind.oneByteString,
+      DataKind.twoByteString,
+    };
+
+    DataKind kind = _readKind();
+    if (boolKinds.contains(kind)) {
+      return expectBool();
+    } else if (kind == DataKind.nil) {
+      checkNull();
+      return null;
+    } else if (intKinds.contains(kind)) {
+      return expectInt();
+    } else if (stringKinds.contains(kind)) {
+      return expectString();
+    } else if (kind == DataKind.startList) {
+      List<Object?> result = [];
+      expectList();
+      while (moveNext()) {
+        Object? element = expectAny();
+        result.add(element);
+      }
+      return result;
+    } else if (kind == DataKind.startMap) {
+      Map<Object?, Object?> result = {};
+      expectMap();
+      while (moveNext()) {
+        Object? key = expectAny();
+        moveNext();
+        Object? value = expectAny();
+        result[key] = value;
+      }
+      return result;
+    } else if (kind == DataKind.uint8List) {
+      return expectUint8List();
+    } else {
+      throw new StateError('Expected: $kind');
+    }
+  }
+
   @override
   bool moveNext() {
     int? increment = _byteOffsetIncrement;
@@ -524,8 +662,9 @@
     _byteOffset += increment;
     if (_byteOffset >= _bytes.lengthInBytes) {
       return false;
-    } else if (_readKind() == DataKind.endList) {
-      // You don't explicitly consume list end markers.
+    } else if (_readKind() == DataKind.endList ||
+        _readKind() == DataKind.endMap) {
+      // You don't explicitly consume list/map end markers.
       _byteOffsetIncrement = 1;
       return false;
     } else {
@@ -541,6 +680,8 @@
   directEncodedUint8, // Encoded in the kind byte.
   startList,
   endList,
+  startMap,
+  endMap,
   int8,
   int16,
   int32,
@@ -552,6 +693,7 @@
   float64,
   oneByteString,
   twoByteString,
+  uint8List,
 }
 
 /// Must be set using `withSerializationMode` before doing any serialization or
diff --git a/pkg/_fe_analyzer_shared/test/macros/compiler/request_channel_test.dart b/pkg/_fe_analyzer_shared/test/macros/compiler/request_channel_test.dart
new file mode 100644
index 0000000..fca8409
--- /dev/null
+++ b/pkg/_fe_analyzer_shared/test/macros/compiler/request_channel_test.dart
@@ -0,0 +1,269 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:io';
+import 'dart:typed_data';
+
+import 'package:_fe_analyzer_shared/src/macros/compiler/request_channel.dart';
+import 'package:_fe_analyzer_shared/src/macros/executor/serialization.dart';
+import 'package:test/test.dart';
+
+main() {
+  group('ByteDataSerializer', () {
+    group('addAny', () {
+      Uint8List write(Object? object) {
+        final serializer = ByteDataSerializer();
+        serializer.addAny(object);
+        return serializer.result;
+      }
+
+      Object? read(Uint8List bytes) {
+        final deserializer = ByteDataDeserializer(
+          new ByteData.sublistView(bytes),
+        );
+        return deserializer.expectAny();
+      }
+
+      void writeRead(Object? object) {
+        final bytes = write(object);
+        expect(read(bytes), object);
+      }
+
+      group('bool', () {
+        test('false', () {
+          writeRead(false);
+        });
+        test('true', () {
+          writeRead(true);
+        });
+      });
+
+      test('null', () {
+        writeRead(null);
+      });
+
+      group('int', () {
+        test('negative', () {
+          void writeReadInt(int value) {
+            expect(value, isNegative);
+            writeRead(value);
+          }
+
+          writeReadInt(-1);
+          writeReadInt(-2);
+          writeReadInt(-0x7F);
+          writeReadInt(-0xFF);
+          writeReadInt(-0xFFFF);
+          writeReadInt(-0xFFFFFF);
+          writeReadInt(-0xFFFFFFFF);
+          writeReadInt(-0xFFFFFFFFFF);
+          writeReadInt(-0xFFFFFFFFFFFF);
+          writeReadInt(-0xFFFFFFFFFFFFFF);
+          writeReadInt(-0x7FFFFFFFFFFFFFFF);
+          writeReadInt(0x8000000000000000);
+        });
+
+        test('non-negative', () {
+          void writeReadInt(int value) {
+            expect(value, isNonNegative);
+            writeRead(value);
+          }
+
+          writeReadInt(0);
+          writeReadInt(1);
+          writeReadInt(0x6F);
+          writeReadInt(0x7F);
+          writeReadInt(0xFF);
+          writeReadInt(0xFFFF);
+          writeReadInt(0xFFFFFF);
+          writeReadInt(0xFFFFFFFF);
+          writeReadInt(0xFFFFFFFFFF);
+          writeReadInt(0xFFFFFFFFFFFF);
+          writeReadInt(0xFFFFFFFFFFFFFF);
+          writeReadInt(0x7FFFFFFFFFFFFFFF);
+        });
+      });
+
+      group('String', () {
+        test('one-byte', () {
+          writeRead('test');
+        });
+        test('two-byte', () {
+          writeRead('проба');
+        });
+      });
+
+      group('list', () {
+        test('empty', () {
+          writeRead(<int>[]);
+        });
+        test('of int', () {
+          writeRead([1, 2, 3]);
+        });
+        test('of string', () {
+          writeRead(['a', 'b', 'c']);
+        });
+        test('mixed', () {
+          writeRead([true, 'abc', 0xAB]);
+        });
+        test('nested', () {
+          writeRead([
+            [1, 2, 3],
+            true,
+            ['foo', 'bar'],
+            false,
+          ]);
+        });
+      });
+
+      group('map', () {
+        test('empty', () {
+          writeRead(<String, int>{});
+        });
+        test('string to int', () {
+          writeRead({'a': 0x11, 'b': 0x22});
+        });
+        test('nested', () {
+          writeRead({
+            'foo': {0: 1, 2: 3},
+            'bar': {4: 5, 6: 7},
+          });
+        });
+      });
+
+      group('Uint8List', () {
+        test('empty', () {
+          writeRead(Uint8List(0));
+        });
+        test('5 elements', () {
+          writeRead(
+            Uint8List.fromList(
+              [0x11, 0x22, 0x33, 0x44, 0x55],
+            ),
+          );
+        });
+        test('0..4096', () {
+          for (int length = 1; length < 4096; length++) {
+            Uint8List object = Uint8List(length);
+            for (int i = 0; i < object.length; i++) {
+              object[i] = i & 0xFF;
+            }
+            Uint8List bytes = write(object);
+            Uint8List readObject = read(bytes) as Uint8List;
+            for (int i = 0; i < object.length; i++) {
+              if (object[i] != readObject[i]) {
+                fail('[length: $length][i: $i]');
+              }
+            }
+          }
+        });
+        test('nested in Map', () {
+          writeRead({
+            'answer': 42,
+            'result': Uint8List.fromList(
+              [0x11, 0x22, 0x33, 0x44, 0x55],
+            ),
+            'hasErrors': false,
+          });
+        });
+      });
+
+      group('JSON', () {
+        test('command', () {
+          writeRead({
+            'command': 'compile',
+            'argument': {
+              'uri': 'input.dart',
+              'additional': ['a', 'b', 'c'],
+            },
+          });
+        });
+      });
+    });
+  });
+
+  group('RequestChannel', () {
+    Future<void> runServerClient({
+      required Future<void> Function(RequestChannel channel) server,
+      required Future<void> Function(RequestChannel channel) client,
+    }) async {
+      ServerSocket serverSocket =
+          await ServerSocket.bind(InternetAddress.loopbackIPv4, 0);
+      serverSocket.listen((socket) async {
+        await serverSocket.close();
+        await server(
+          RequestChannel(socket),
+        );
+      });
+
+      var clientSocket = await Socket.connect(
+        InternetAddress.loopbackIPv4,
+        serverSocket.port,
+      );
+      try {
+        await client(
+          RequestChannel(clientSocket),
+        );
+      } finally {
+        clientSocket.destroy();
+      }
+    }
+
+    test('exception', () async {
+      await runServerClient(
+        server: (channel) async {
+          channel.add('throwIt', (argument) async {
+            throw 'Some error';
+          });
+        },
+        client: (channel) async {
+          try {
+            await channel.sendRequest('throwIt', {});
+            fail('Expected to throw RemoteException.');
+          } on RemoteException catch (e) {
+            expect(e.message, 'Some error');
+            expect(e.stackTrace, isNotEmpty);
+          }
+        },
+      );
+    });
+
+    test('no handler', () async {
+      await runServerClient(
+        server: (channel) async {},
+        client: (channel) async {
+          try {
+            await channel.sendRequest('noSuchHandler', {});
+            fail('Expected to throw RemoteException.');
+          } on RemoteException catch (e) {
+            expect(e.message, contains('noSuchHandler'));
+            expect(e.stackTrace, isNotEmpty);
+          }
+        },
+      );
+    });
+
+    test('two-way communication', () async {
+      await runServerClient(
+        server: (channel) async {
+          channel.add('add', (argument) async {
+            if (argument is List<Object?> && argument.length == 2) {
+              Object? a = argument[0];
+              Object? b = argument[1];
+              if (a is int && b is int) {
+                int more = await channel.sendRequest<int>('more', null);
+                return a + b + more;
+              }
+            }
+            return '<bad>';
+          });
+        },
+        client: (channel) async {
+          channel.add('more', (_) async => 4);
+          expect(await channel.sendRequest('add', [2, 3]), 9);
+        },
+      );
+    });
+  });
+}
diff --git a/pkg/analysis_server/lib/src/analysis_server.dart b/pkg/analysis_server/lib/src/analysis_server.dart
index 2aec83b..ba6dc60 100644
--- a/pkg/analysis_server/lib/src/analysis_server.dart
+++ b/pkg/analysis_server/lib/src/analysis_server.dart
@@ -39,6 +39,7 @@
 import 'package:analysis_server/src/server/error_notifier.dart';
 import 'package:analysis_server/src/server/features.dart';
 import 'package:analysis_server/src/server/sdk_configuration.dart';
+import 'package:analysis_server/src/services/execution/execution_context.dart';
 import 'package:analysis_server/src/services/flutter/widget_descriptions.dart';
 import 'package:analysis_server/src/utilities/process.dart';
 import 'package:analysis_server/src/utilities/progress.dart';
@@ -107,6 +108,9 @@
   /// The support for Flutter properties.
   WidgetDescriptions flutterWidgetDescriptions = WidgetDescriptions();
 
+  /// The context used by the execution domain handlers.
+  final ExecutionContext executionContext = ExecutionContext();
+
   /// The [Completer] that completes when analysis is complete.
   Completer<void>? _onAnalysisCompleteCompleter;
 
@@ -221,7 +225,7 @@
       EditDomainHandler(this),
       SearchDomainHandler(this),
       CompletionDomainHandler(this),
-      ExecutionDomainHandler(this),
+      ExecutionDomainHandler(this, executionContext),
       DiagnosticDomainHandler(this),
       AnalyticsDomainHandler(this),
       KytheDomainHandler(this),
diff --git a/pkg/analysis_server/lib/src/domain_execution.dart b/pkg/analysis_server/lib/src/domain_execution.dart
index c1e1b35..43f7db0 100644
--- a/pkg/analysis_server/lib/src/domain_execution.dart
+++ b/pkg/analysis_server/lib/src/domain_execution.dart
@@ -2,13 +2,16 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-import 'dart:collection';
-
 import 'package:analysis_server/protocol/protocol_constants.dart';
 import 'package:analysis_server/src/analysis_server.dart';
+import 'package:analysis_server/src/handler/legacy/execution_create_context.dart';
+import 'package:analysis_server/src/handler/legacy/execution_delete_context.dart';
+import 'package:analysis_server/src/handler/legacy/execution_get_suggestions.dart';
+import 'package:analysis_server/src/handler/legacy/execution_map_uri.dart';
+import 'package:analysis_server/src/handler/legacy/execution_set_subscriptions.dart';
 import 'package:analysis_server/src/protocol_server.dart';
+import 'package:analysis_server/src/services/execution/execution_context.dart';
 import 'package:analysis_server/src/utilities/progress.dart';
-import 'package:analyzer/file_system/file_system.dart';
 
 /// Instances of the class [ExecutionDomainHandler] implement a [RequestHandler]
 /// that handles requests in the `execution` domain.
@@ -16,57 +19,12 @@
   /// The analysis server that is using this handler to process requests.
   final AnalysisServer server;
 
-  /// The next execution context identifier to be returned.
-  int nextContextId = 0;
-
-  /// A table mapping execution context id's to the root of the context.
-  final Map<String, String> contextMap = HashMap<String, String>();
+  /// The context used by the execution domain handlers.
+  final ExecutionContext executionContext;
 
   /// Initialize a newly created handler to handle requests for the given
   /// [server].
-  ExecutionDomainHandler(this.server);
-
-  /// Implement the `execution.createContext` request.
-  Response createContext(Request request) {
-    var file = ExecutionCreateContextParams.fromRequest(request).contextRoot;
-    var contextId = (nextContextId++).toString();
-    contextMap[contextId] = file;
-    return ExecutionCreateContextResult(contextId).toResponse(request.id);
-  }
-
-  /// Implement the `execution.deleteContext` request.
-  Response deleteContext(Request request) {
-    var contextId = ExecutionDeleteContextParams.fromRequest(request).id;
-    contextMap.remove(contextId);
-    return ExecutionDeleteContextResult().toResponse(request.id);
-  }
-
-  /// Implement the 'execution.getSuggestions' request.
-  void getSuggestions(Request request) async {
-//    var params = new ExecutionGetSuggestionsParams.fromRequest(request);
-//    var computer = new RuntimeCompletionComputer(
-//        server.resourceProvider,
-//        server.fileContentOverlay,
-//        server.getAnalysisDriver(params.contextFile),
-//        params.code,
-//        params.offset,
-//        params.contextFile,
-//        params.contextOffset,
-//        params.variables,
-//        params.expressions);
-//    RuntimeCompletionResult completionResult = await computer.compute();
-//
-//    // Send the response.
-//    var result = new ExecutionGetSuggestionsResult(
-//        suggestions: completionResult.suggestions,
-//        expressions: completionResult.expressions);
-    // TODO(brianwilkerson) Re-enable this functionality after implementing a
-    // way of computing suggestions that is compatible with AnalysisSession.
-    var result = ExecutionGetSuggestionsResult(
-        suggestions: <CompletionSuggestion>[],
-        expressions: <RuntimeCompletionExpression>[]);
-    server.sendResponse(result.toResponse(request.id));
-  }
+  ExecutionDomainHandler(this.server, this.executionContext);
 
   @override
   Response? handleRequest(
@@ -74,76 +32,32 @@
     try {
       var requestName = request.method;
       if (requestName == EXECUTION_REQUEST_CREATE_CONTEXT) {
-        return createContext(request);
+        ExecutionCreateContextHandler(
+                server, request, cancellationToken, executionContext)
+            .handle();
+        return Response.DELAYED_RESPONSE;
       } else if (requestName == EXECUTION_REQUEST_DELETE_CONTEXT) {
-        return deleteContext(request);
+        ExecutionDeleteContextHandler(
+                server, request, cancellationToken, executionContext)
+            .handle();
+        return Response.DELAYED_RESPONSE;
       } else if (requestName == EXECUTION_REQUEST_GET_SUGGESTIONS) {
-        getSuggestions(request);
+        ExecutionGetSuggestionsHandler(server, request, cancellationToken)
+            .handle();
         return Response.DELAYED_RESPONSE;
       } else if (requestName == EXECUTION_REQUEST_MAP_URI) {
-        return mapUri(request);
+        ExecutionMapUriHandler(
+                server, request, cancellationToken, executionContext)
+            .handle();
+        return Response.DELAYED_RESPONSE;
       } else if (requestName == EXECUTION_REQUEST_SET_SUBSCRIPTIONS) {
-        return setSubscriptions(request);
+        ExecutionSetSubscriptionsHandler(server, request, cancellationToken)
+            .handle();
+        return Response.DELAYED_RESPONSE;
       }
     } on RequestFailure catch (exception) {
       return exception.response;
     }
     return null;
   }
-
-  /// Implement the 'execution.mapUri' request.
-  Response mapUri(Request request) {
-    var params = ExecutionMapUriParams.fromRequest(request);
-    var contextId = params.id;
-    var path = contextMap[contextId];
-    if (path == null) {
-      return Response.invalidParameter(request, 'id',
-          'There is no execution context with an id of $contextId');
-    }
-
-    var driver = server.getAnalysisDriver(path);
-    if (driver == null) {
-      return Response.invalidExecutionContext(request, contextId);
-    }
-    var sourceFactory = driver.sourceFactory;
-
-    var file = params.file;
-    var uri = params.uri;
-    if (file != null) {
-      if (uri != null) {
-        return Response.invalidParameter(request, 'file',
-            'Either file or uri must be provided, but not both');
-      }
-      var resource = server.resourceProvider.getResource(file);
-      if (!resource.exists) {
-        return Response.invalidParameter(request, 'file', 'Must exist');
-      } else if (resource is! File) {
-        return Response.invalidParameter(
-            request, 'file', 'Must not refer to a directory');
-      }
-
-      var source = driver.fsState.getFileForPath(file).source;
-      if (!source.uri.isScheme('file')) {
-        uri = source.uri.toString();
-      } else {
-        uri = sourceFactory.pathToUri(file).toString();
-      }
-      return ExecutionMapUriResult(uri: uri).toResponse(request.id);
-    } else if (uri != null) {
-      var source = sourceFactory.forUri(uri);
-      if (source == null) {
-        return Response.invalidParameter(request, 'uri', 'Invalid URI');
-      }
-      file = source.fullName;
-      return ExecutionMapUriResult(file: file).toResponse(request.id);
-    }
-    return Response.invalidParameter(
-        request, 'file', 'Either file or uri must be provided');
-  }
-
-  /// Implement the 'execution.setSubscriptions' request.
-  Response setSubscriptions(Request request) {
-    // Under the analysis driver, setSubscriptions() becomes a no-op.
-    return ExecutionSetSubscriptionsResult().toResponse(request.id);
-  }
 }
diff --git a/pkg/analysis_server/lib/src/domain_kythe.dart b/pkg/analysis_server/lib/src/domain_kythe.dart
index 0be62d1..afa3c9e 100644
--- a/pkg/analysis_server/lib/src/domain_kythe.dart
+++ b/pkg/analysis_server/lib/src/domain_kythe.dart
@@ -4,15 +4,10 @@
 
 import 'package:analysis_server/protocol/protocol.dart';
 import 'package:analysis_server/protocol/protocol_constants.dart';
-import 'package:analysis_server/protocol/protocol_generated.dart';
 import 'package:analysis_server/src/analysis_server.dart';
 import 'package:analysis_server/src/domain_abstract.dart';
-import 'package:analysis_server/src/plugin/result_merger.dart';
-import 'package:analysis_server/src/services/kythe/kythe_visitors.dart';
+import 'package:analysis_server/src/handler/legacy/kythe_get_kythe_entries.dart';
 import 'package:analysis_server/src/utilities/progress.dart';
-import 'package:analyzer/src/dart/element/inheritance_manager3.dart';
-import 'package:analyzer_plugin/protocol/protocol_common.dart';
-import 'package:analyzer_plugin/protocol/protocol_generated.dart' as plugin;
 
 /// Instances of the class [KytheDomainHandler] implement a [RequestHandler]
 /// that handles requests in the `kythe` domain.
@@ -21,62 +16,14 @@
   /// [server].
   KytheDomainHandler(AnalysisServer server) : super(server);
 
-  /// Implement the `kythe.getKytheEntries` request.
-  Future<void> getKytheEntries(Request request) async {
-    var file = KytheGetKytheEntriesParams.fromRequest(request).file;
-    var driver = server.getAnalysisDriver(file);
-    if (driver == null) {
-      server.sendResponse(Response.getKytheEntriesInvalidFile(request));
-    } else {
-      //
-      // Allow plugins to start computing entries.
-      //
-      var requestParams = plugin.KytheGetKytheEntriesParams(file);
-      var pluginFutures = server.pluginManager.broadcastRequest(
-        requestParams,
-        contextRoot: driver.analysisContext!.contextRoot,
-      );
-      //
-      // Compute entries generated by server.
-      //
-      var allResults = <KytheGetKytheEntriesResult>[];
-      var result = await server.getResolvedUnit(file);
-      if (result != null) {
-        var entries = <KytheEntry>[];
-        // TODO(brianwilkerson) Figure out how to get the list of files.
-        var files = <String>[];
-        result.unit.accept(KytheDartVisitor(server.resourceProvider, entries,
-            file, InheritanceManager3(), result.content));
-        allResults.add(KytheGetKytheEntriesResult(entries, files));
-      }
-      //
-      // Add the entries produced by plugins to the server-generated entries.
-      //
-      var responses = await waitForResponses(pluginFutures,
-          requestParameters: requestParams);
-      for (var response in responses) {
-        var result = plugin.KytheGetKytheEntriesResult.fromResponse(response);
-        allResults
-            .add(KytheGetKytheEntriesResult(result.entries, result.files));
-      }
-      //
-      // Return the result.
-      //
-      var merger = ResultMerger();
-      var mergedResults = merger.mergeKytheEntries(allResults);
-      server.sendResponse(
-          KytheGetKytheEntriesResult(mergedResults.entries, mergedResults.files)
-              .toResponse(request.id));
-    }
-  }
-
   @override
   Response? handleRequest(
       Request request, CancellationToken cancellationToken) {
     try {
       var requestName = request.method;
       if (requestName == KYTHE_REQUEST_GET_KYTHE_ENTRIES) {
-        getKytheEntries(request);
+        KytheGetKytheEntriesHandler(server, request, cancellationToken)
+            .handle();
         return Response.DELAYED_RESPONSE;
       }
     } on RequestFailure catch (exception) {
diff --git a/pkg/analysis_server/lib/src/handler/legacy/execution_create_context.dart b/pkg/analysis_server/lib/src/handler/legacy/execution_create_context.dart
new file mode 100644
index 0000000..3c29df2
--- /dev/null
+++ b/pkg/analysis_server/lib/src/handler/legacy/execution_create_context.dart
@@ -0,0 +1,32 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+
+import 'package:analysis_server/protocol/protocol.dart';
+import 'package:analysis_server/protocol/protocol_generated.dart';
+import 'package:analysis_server/src/analysis_server.dart';
+import 'package:analysis_server/src/handler/legacy/legacy_handler.dart';
+import 'package:analysis_server/src/services/execution/execution_context.dart';
+import 'package:analysis_server/src/utilities/progress.dart';
+
+/// The handler for the `execution.createContext` request.
+class ExecutionCreateContextHandler extends LegacyHandler {
+  /// The context used by the execution domain handlers.
+  final ExecutionContext executionContext;
+
+  /// Initialize a newly created handler to be able to service requests for the
+  /// [server].
+  ExecutionCreateContextHandler(AnalysisServer server, Request request,
+      CancellationToken cancellationToken, this.executionContext)
+      : super(server, request, cancellationToken);
+
+  @override
+  Future<void> handle() async {
+    var file = ExecutionCreateContextParams.fromRequest(request).contextRoot;
+    var contextId = (executionContext.nextContextId++).toString();
+    executionContext.contextMap[contextId] = file;
+    sendResult(ExecutionCreateContextResult(contextId));
+  }
+}
diff --git a/pkg/analysis_server/lib/src/handler/legacy/execution_delete_context.dart b/pkg/analysis_server/lib/src/handler/legacy/execution_delete_context.dart
new file mode 100644
index 0000000..5802dde
--- /dev/null
+++ b/pkg/analysis_server/lib/src/handler/legacy/execution_delete_context.dart
@@ -0,0 +1,31 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+
+import 'package:analysis_server/protocol/protocol.dart';
+import 'package:analysis_server/protocol/protocol_generated.dart';
+import 'package:analysis_server/src/analysis_server.dart';
+import 'package:analysis_server/src/handler/legacy/legacy_handler.dart';
+import 'package:analysis_server/src/services/execution/execution_context.dart';
+import 'package:analysis_server/src/utilities/progress.dart';
+
+/// The handler for the `execution.deleteContext` request.
+class ExecutionDeleteContextHandler extends LegacyHandler {
+  /// The context used by the execution domain handlers.
+  final ExecutionContext executionContext;
+
+  /// Initialize a newly created handler to be able to service requests for the
+  /// [server].
+  ExecutionDeleteContextHandler(AnalysisServer server, Request request,
+      CancellationToken cancellationToken, this.executionContext)
+      : super(server, request, cancellationToken);
+
+  @override
+  Future<void> handle() async {
+    var contextId = ExecutionDeleteContextParams.fromRequest(request).id;
+    executionContext.contextMap.remove(contextId);
+    sendResult(ExecutionDeleteContextResult());
+  }
+}
diff --git a/pkg/analysis_server/lib/src/handler/legacy/execution_get_suggestions.dart b/pkg/analysis_server/lib/src/handler/legacy/execution_get_suggestions.dart
new file mode 100644
index 0000000..d7413f2
--- /dev/null
+++ b/pkg/analysis_server/lib/src/handler/legacy/execution_get_suggestions.dart
@@ -0,0 +1,48 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+
+import 'package:analysis_server/protocol/protocol.dart';
+import 'package:analysis_server/protocol/protocol_generated.dart';
+import 'package:analysis_server/src/analysis_server.dart';
+import 'package:analysis_server/src/handler/legacy/legacy_handler.dart';
+import 'package:analysis_server/src/utilities/progress.dart';
+import 'package:analyzer_plugin/protocol/protocol_common.dart';
+
+/// The handler for the `execution.getSuggestions` request.
+class ExecutionGetSuggestionsHandler extends LegacyHandler {
+  /// Initialize a newly created handler to be able to service requests for the
+  /// [server].
+  ExecutionGetSuggestionsHandler(AnalysisServer server, Request request,
+      CancellationToken cancellationToken)
+      : super(server, request, cancellationToken);
+
+  @override
+  Future<void> handle() async {
+//    var params = new ExecutionGetSuggestionsParams.fromRequest(request);
+//    var computer = new RuntimeCompletionComputer(
+//        server.resourceProvider,
+//        server.fileContentOverlay,
+//        server.getAnalysisDriver(params.contextFile),
+//        params.code,
+//        params.offset,
+//        params.contextFile,
+//        params.contextOffset,
+//        params.variables,
+//        params.expressions);
+//    RuntimeCompletionResult completionResult = await computer.compute();
+//
+//    // Send the response.
+//    var result = new ExecutionGetSuggestionsResult(
+//        suggestions: completionResult.suggestions,
+//        expressions: completionResult.expressions);
+    // TODO(brianwilkerson) Re-enable this functionality after implementing a
+    // way of computing suggestions that is compatible with AnalysisSession.
+    var result = ExecutionGetSuggestionsResult(
+        suggestions: <CompletionSuggestion>[],
+        expressions: <RuntimeCompletionExpression>[]);
+    sendResponse(result.toResponse(request.id));
+  }
+}
diff --git a/pkg/analysis_server/lib/src/handler/legacy/execution_map_uri.dart b/pkg/analysis_server/lib/src/handler/legacy/execution_map_uri.dart
new file mode 100644
index 0000000..0e84b04
--- /dev/null
+++ b/pkg/analysis_server/lib/src/handler/legacy/execution_map_uri.dart
@@ -0,0 +1,83 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+
+import 'package:analysis_server/protocol/protocol.dart';
+import 'package:analysis_server/protocol/protocol_generated.dart';
+import 'package:analysis_server/src/analysis_server.dart';
+import 'package:analysis_server/src/handler/legacy/legacy_handler.dart';
+import 'package:analysis_server/src/services/execution/execution_context.dart';
+import 'package:analysis_server/src/utilities/progress.dart';
+import 'package:analyzer/file_system/file_system.dart';
+
+/// The handler for the `execution.mapUri` request.
+class ExecutionMapUriHandler extends LegacyHandler {
+  /// The context used by the execution domain handlers.
+  final ExecutionContext executionContext;
+
+  /// Initialize a newly created handler to be able to service requests for the
+  /// [server].
+  ExecutionMapUriHandler(AnalysisServer server, Request request,
+      CancellationToken cancellationToken, this.executionContext)
+      : super(server, request, cancellationToken);
+
+  @override
+  Future<void> handle() async {
+    var params = ExecutionMapUriParams.fromRequest(request);
+    var contextId = params.id;
+    var path = executionContext.contextMap[contextId];
+    if (path == null) {
+      sendResponse(Response.invalidParameter(request, 'id',
+          'There is no execution context with an id of $contextId'));
+      return;
+    }
+
+    var driver = server.getAnalysisDriver(path);
+    if (driver == null) {
+      sendResponse(Response.invalidExecutionContext(request, contextId));
+      return;
+    }
+    var sourceFactory = driver.sourceFactory;
+
+    var file = params.file;
+    var uri = params.uri;
+    if (file != null) {
+      if (uri != null) {
+        sendResponse(Response.invalidParameter(request, 'file',
+            'Either file or uri must be provided, but not both'));
+        return;
+      }
+      var resource = server.resourceProvider.getResource(file);
+      if (!resource.exists) {
+        sendResponse(Response.invalidParameter(request, 'file', 'Must exist'));
+        return;
+      } else if (resource is! File) {
+        sendResponse(Response.invalidParameter(
+            request, 'file', 'Must not refer to a directory'));
+        return;
+      }
+
+      var source = driver.fsState.getFileForPath(file).source;
+      if (!source.uri.isScheme('file')) {
+        uri = source.uri.toString();
+      } else {
+        uri = sourceFactory.pathToUri(file).toString();
+      }
+      sendResult(ExecutionMapUriResult(uri: uri));
+      return;
+    } else if (uri != null) {
+      var source = sourceFactory.forUri(uri);
+      if (source == null) {
+        sendResponse(Response.invalidParameter(request, 'uri', 'Invalid URI'));
+        return;
+      }
+      file = source.fullName;
+      sendResult(ExecutionMapUriResult(file: file));
+      return;
+    }
+    sendResponse(Response.invalidParameter(
+        request, 'file', 'Either file or uri must be provided'));
+  }
+}
diff --git a/pkg/analysis_server/lib/src/handler/legacy/execution_set_subscriptions.dart b/pkg/analysis_server/lib/src/handler/legacy/execution_set_subscriptions.dart
new file mode 100644
index 0000000..480f100a
--- /dev/null
+++ b/pkg/analysis_server/lib/src/handler/legacy/execution_set_subscriptions.dart
@@ -0,0 +1,26 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+
+import 'package:analysis_server/protocol/protocol.dart';
+import 'package:analysis_server/protocol/protocol_generated.dart';
+import 'package:analysis_server/src/analysis_server.dart';
+import 'package:analysis_server/src/handler/legacy/legacy_handler.dart';
+import 'package:analysis_server/src/utilities/progress.dart';
+
+/// The handler for the `execution.setSubscriptions` request.
+class ExecutionSetSubscriptionsHandler extends LegacyHandler {
+  /// Initialize a newly created handler to be able to service requests for the
+  /// [server].
+  ExecutionSetSubscriptionsHandler(AnalysisServer server, Request request,
+      CancellationToken cancellationToken)
+      : super(server, request, cancellationToken);
+
+  @override
+  Future<void> handle() async {
+    // Under the analysis driver, setSubscriptions becomes a no-op.
+    sendResult(ExecutionSetSubscriptionsResult());
+  }
+}
diff --git a/pkg/analysis_server/lib/src/handler/legacy/kythe_get_kythe_entries.dart b/pkg/analysis_server/lib/src/handler/legacy/kythe_get_kythe_entries.dart
new file mode 100644
index 0000000..e9a57e8
--- /dev/null
+++ b/pkg/analysis_server/lib/src/handler/legacy/kythe_get_kythe_entries.dart
@@ -0,0 +1,75 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+
+import 'package:analysis_server/protocol/protocol.dart';
+import 'package:analysis_server/protocol/protocol_generated.dart';
+import 'package:analysis_server/src/analysis_server.dart';
+import 'package:analysis_server/src/domain_abstract.dart';
+import 'package:analysis_server/src/handler/legacy/legacy_handler.dart';
+import 'package:analysis_server/src/plugin/result_merger.dart';
+import 'package:analysis_server/src/services/kythe/kythe_visitors.dart';
+import 'package:analysis_server/src/utilities/progress.dart';
+import 'package:analyzer/src/dart/element/inheritance_manager3.dart';
+import 'package:analyzer_plugin/protocol/protocol_common.dart';
+import 'package:analyzer_plugin/protocol/protocol_generated.dart' as plugin;
+
+/// The handler for the `kythe.getKytheEntries` request.
+class KytheGetKytheEntriesHandler extends LegacyHandler
+    with RequestHandlerMixin<AnalysisServer> {
+  /// Initialize a newly created handler to be able to service requests for the
+  /// [server].
+  KytheGetKytheEntriesHandler(AnalysisServer server, Request request,
+      CancellationToken cancellationToken)
+      : super(server, request, cancellationToken);
+
+  @override
+  Future<void> handle() async {
+    var file = KytheGetKytheEntriesParams.fromRequest(request).file;
+    var driver = server.getAnalysisDriver(file);
+    if (driver == null) {
+      sendResponse(Response.getKytheEntriesInvalidFile(request));
+    } else {
+      //
+      // Allow plugins to start computing entries.
+      //
+      var requestParams = plugin.KytheGetKytheEntriesParams(file);
+      var pluginFutures = server.pluginManager.broadcastRequest(
+        requestParams,
+        contextRoot: driver.analysisContext!.contextRoot,
+      );
+      //
+      // Compute entries generated by server.
+      //
+      var allResults = <KytheGetKytheEntriesResult>[];
+      var result = await server.getResolvedUnit(file);
+      if (result != null) {
+        var entries = <KytheEntry>[];
+        // TODO(brianwilkerson) Figure out how to get the list of files.
+        var files = <String>[];
+        result.unit.accept(KytheDartVisitor(server.resourceProvider, entries,
+            file, InheritanceManager3(), result.content));
+        allResults.add(KytheGetKytheEntriesResult(entries, files));
+      }
+      //
+      // Add the entries produced by plugins to the server-generated entries.
+      //
+      var responses = await waitForResponses(pluginFutures,
+          requestParameters: requestParams);
+      for (var response in responses) {
+        var result = plugin.KytheGetKytheEntriesResult.fromResponse(response);
+        allResults
+            .add(KytheGetKytheEntriesResult(result.entries, result.files));
+      }
+      //
+      // Return the result.
+      //
+      var merger = ResultMerger();
+      var mergedResults = merger.mergeKytheEntries(allResults);
+      sendResult(KytheGetKytheEntriesResult(
+          mergedResults.entries, mergedResults.files));
+    }
+  }
+}
diff --git a/pkg/analysis_server/lib/src/services/execution/execution_context.dart b/pkg/analysis_server/lib/src/services/execution/execution_context.dart
new file mode 100644
index 0000000..b3b9dd3
--- /dev/null
+++ b/pkg/analysis_server/lib/src/services/execution/execution_context.dart
@@ -0,0 +1,14 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+class ExecutionContext {
+  /// The next execution context identifier to be returned.
+  int nextContextId = 0;
+
+  /// A table mapping execution context id's to the root of the context.
+  final Map<String, String> contextMap = {};
+
+  /// Initialize a newly created execution context.
+  ExecutionContext();
+}
diff --git a/pkg/analysis_server/test/domain_execution_test.dart b/pkg/analysis_server/test/domain_execution_test.dart
index 3bfafaf..42550dc 100644
--- a/pkg/analysis_server/test/domain_execution_test.dart
+++ b/pkg/analysis_server/test/domain_execution_test.dart
@@ -3,15 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'package:analysis_server/protocol/protocol_generated.dart';
-import 'package:analysis_server/src/analysis_server.dart';
 import 'package:analysis_server/src/domain_execution.dart';
 import 'package:analysis_server/src/protocol_server.dart';
-import 'package:analysis_server/src/server/crash_reporting_attachments.dart';
-import 'package:analysis_server/src/utilities/mocks.dart';
-import 'package:analysis_server/src/utilities/progress.dart';
-import 'package:analyzer/file_system/memory_file_system.dart';
-import 'package:analyzer/instrumentation/instrumentation.dart';
-import 'package:analyzer/src/generated/sdk.dart';
 import 'package:test/test.dart';
 import 'package:test_reflective_loader/test_reflective_loader.dart';
 
@@ -22,59 +15,24 @@
   defineReflectiveSuite(() {
     defineReflectiveTests(ExecutionDomainTest);
   });
-  group('ExecutionDomainHandler', () {
-    var provider = MemoryResourceProvider();
-    late AnalysisServer server;
-    late ExecutionDomainHandler handler;
-
-    setUp(() {
-      server = AnalysisServer(
-          MockServerChannel(),
-          provider,
-          AnalysisServerOptions(),
-          DartSdkManager(''),
-          CrashReportingAttachmentsBuilder.empty,
-          InstrumentationService.NULL_SERVICE);
-      handler = ExecutionDomainHandler(server);
-    });
-
-    group('createContext/deleteContext', () {
-      test('create/delete multiple contexts', () {
-        var request = ExecutionCreateContextParams('/a/b.dart').toRequest('0');
-        var response = handler.handleRequest(request, NotCancelableToken())!;
-        expect(response, isResponseSuccess('0'));
-        var result = ExecutionCreateContextResult.fromResponse(response);
-        var id0 = result.id;
-
-        request = ExecutionCreateContextParams('/c/d.dart').toRequest('1');
-        response = handler.handleRequest(request, NotCancelableToken())!;
-        expect(response, isResponseSuccess('1'));
-        result = ExecutionCreateContextResult.fromResponse(response);
-        var id1 = result.id;
-
-        expect(id0 == id1, isFalse);
-
-        request = ExecutionDeleteContextParams(id0).toRequest('2');
-        response = handler.handleRequest(request, NotCancelableToken())!;
-        expect(response, isResponseSuccess('2'));
-
-        request = ExecutionDeleteContextParams(id1).toRequest('3');
-        response = handler.handleRequest(request, NotCancelableToken())!;
-        expect(response, isResponseSuccess('3'));
-      });
-
-      test('delete non-existent context', () {
-        var request = ExecutionDeleteContextParams('13').toRequest('0');
-        var response = handler.handleRequest(request, NotCancelableToken());
-        // TODO(brianwilkerson) It isn't currently specified to be an error if a
-        // client attempts to delete a context that doesn't exist. Should it be?
-//        expect(response, isResponseFailure('0'));
-        expect(response, isResponseSuccess('0'));
-      });
-    });
-
-    // TODO(brianwilkerson) Re-enable these tests if we re-enable the
-    // execution.mapUri request.
+  // group('ExecutionDomainHandler', () {
+  //   var provider = MemoryResourceProvider();
+  //   late AnalysisServer server;
+  //   late ExecutionDomainHandler handler;
+  //
+  //   setUp(() {
+  //     server = AnalysisServer(
+  //         MockServerChannel(),
+  //         provider,
+  //         AnalysisServerOptions(),
+  //         DartSdkManager(''),
+  //         CrashReportingAttachmentsBuilder.empty,
+  //         InstrumentationService.NULL_SERVICE);
+  //     handler = ExecutionDomainHandler(server, server.executionContext);
+  //   });
+  //
+  //  // TODO(brianwilkerson) Re-enable these tests if we re-enable the
+  //  // execution.mapUri request.
 //    group('mapUri', () {
 //      String contextId;
 //
@@ -155,7 +113,7 @@
 //        expect(response, isResponseFailure('6'));
 //      });
 //    });
-  });
+//   });
 }
 
 @reflectiveTest
@@ -166,16 +124,49 @@
   Future<void> setUp() async {
     super.setUp();
     await createProject();
-    handler = ExecutionDomainHandler(server);
-    _createExecutionContext(testFile);
+    handler = ExecutionDomainHandler(server, server.executionContext);
+    await _createExecutionContext(testFile);
   }
 
   @override
-  void tearDown() {
-    _disposeExecutionContext();
+  Future<void> tearDown() async {
+    await _disposeExecutionContext();
     super.tearDown();
   }
 
+  Future<void> test_createAndDeleteMultipleContexts() async {
+    var request = ExecutionCreateContextParams('/a/b.dart').toRequest('0');
+    var response = await waitResponse(request);
+    expect(response, isResponseSuccess('0'));
+    var result = ExecutionCreateContextResult.fromResponse(response);
+    var id0 = result.id;
+
+    request = ExecutionCreateContextParams('/c/d.dart').toRequest('1');
+    response = await waitResponse(request);
+    expect(response, isResponseSuccess('1'));
+    result = ExecutionCreateContextResult.fromResponse(response);
+    var id1 = result.id;
+
+    expect(id0 == id1, isFalse);
+
+    request = ExecutionDeleteContextParams(id0).toRequest('2');
+    response = await waitResponse(request);
+    expect(response, isResponseSuccess('2'));
+
+    request = ExecutionDeleteContextParams(id1).toRequest('3');
+    response = await waitResponse(request);
+    expect(response, isResponseSuccess('3'));
+  }
+
+  Future<void> test_deleteNonExistentContext() async {
+    var request = ExecutionDeleteContextParams('13').toRequest('0');
+    var response = await waitResponse(request);
+    // TODO(brianwilkerson) It isn't currently specified to be an error if a
+    // client attempts to delete a context that doesn't exist. Should it be?
+//        expect(response, isResponseFailure('0'));
+    expect(response, isResponseSuccess('0'));
+  }
+
   Future<void> test_getSuggestions() async {
     var code = r'''
 class A {
@@ -212,48 +203,48 @@
     expect(result.suggestions, isEmpty);
   }
 
-  void test_mapUri_file() {
+  Future<void> test_mapUri_file() async {
     var path = newFile2('/a/b.dart', '').path;
     // map the file
-    var result = _mapUri(file: path);
+    var result = await _mapUri(file: path);
     expect(result.file, isNull);
     expect(result.uri, Uri.file(path).toString());
   }
 
-  void test_mapUri_file_dartUriKind() {
-    var path = _mapUri(uri: 'dart:async').file;
+  Future<void> test_mapUri_file_dartUriKind() async {
+    var path = (await _mapUri(uri: 'dart:async')).file;
     // map file
-    var result = _mapUri(file: path);
+    var result = await _mapUri(file: path);
     expect(result.file, isNull);
     expect(result.uri, 'dart:async');
   }
 
-  void test_mapUri_uri() {
+  Future<void> test_mapUri_uri() async {
     var path = newFile2('/a/b.dart', '').path;
     // map the uri
-    var result = _mapUri(uri: Uri.file(path).toString());
+    var result = await _mapUri(uri: Uri.file(path).toString());
     expect(result.file, convertPath('/a/b.dart'));
     expect(result.uri, isNull);
   }
 
-  void _createExecutionContext(String path) {
+  Future<void> _createExecutionContext(String path) async {
     var request = ExecutionCreateContextParams(path).toRequest('0');
-    var response = handler.handleRequest(request, NotCancelableToken())!;
+    var response = await waitResponse(request);
     expect(response, isResponseSuccess('0'));
     var result = ExecutionCreateContextResult.fromResponse(response);
     contextId = result.id;
   }
 
-  void _disposeExecutionContext() {
+  Future<void> _disposeExecutionContext() async {
     var request = ExecutionDeleteContextParams(contextId).toRequest('1');
-    var response = handler.handleRequest(request, NotCancelableToken());
+    var response = await waitResponse(request);
     expect(response, isResponseSuccess('1'));
   }
 
-  ExecutionMapUriResult _mapUri({String? file, String? uri}) {
+  Future<ExecutionMapUriResult> _mapUri({String? file, String? uri}) async {
     var request =
         ExecutionMapUriParams(contextId, file: file, uri: uri).toRequest('2');
-    var response = handler.handleRequest(request, NotCancelableToken())!;
+    var response = await waitResponse(request);
     expect(response, isResponseSuccess('2'));
     return ExecutionMapUriResult.fromResponse(response);
   }
diff --git a/pkg/analyzer/lib/src/workspace/bazel.dart b/pkg/analyzer/lib/src/workspace/bazel.dart
index f21475c..0e2aa32 100644
--- a/pkg/analyzer/lib/src/workspace/bazel.dart
+++ b/pkg/analyzer/lib/src/workspace/bazel.dart
@@ -122,7 +122,12 @@
     }
 
     String packageName = uriPath.substring(0, slash);
+
     String fileUriPart = uriPath.substring(slash + 1);
+    if (fileUriPart.isEmpty) {
+      return null;
+    }
+
     String filePath = fileUriPart.replaceAll('/', _context.separator);
 
     if (!packageName.contains('.')) {
diff --git a/pkg/analyzer/test/src/workspace/bazel_test.dart b/pkg/analyzer/test/src/workspace/bazel_test.dart
index 2aabbdc..b29037d 100644
--- a/pkg/analyzer/test/src/workspace/bazel_test.dart
+++ b/pkg/analyzer/test/src/workspace/bazel_test.dart
@@ -331,6 +331,15 @@
     expect(source, isNull);
   }
 
+  void test_resolveAbsolute_null_emptyFileUriPart() {
+    _addResources([
+      '/workspace/WORKSPACE',
+    ]);
+    var uri = Uri.parse('package:foo.bar/');
+    var source = resolver.resolveAbsolute(uri);
+    expect(source, isNull);
+  }
+
   void test_resolveAbsolute_null_noSlash() {
     _addResources([
       '/workspace/WORKSPACE',
diff --git a/pkg/compiler/lib/src/kernel/transformations/async_lowering.dart b/pkg/compiler/lib/src/kernel/transformations/async_lowering.dart
new file mode 100644
index 0000000..ec145b0
--- /dev/null
+++ b/pkg/compiler/lib/src/kernel/transformations/async_lowering.dart
@@ -0,0 +1,111 @@
+// Copyright (c) 2022, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// @dart=2.12
+
+import 'package:kernel/ast.dart';
+import 'package:kernel/core_types.dart';
+import 'package:kernel/kernel.dart';
+
+class _FunctionData {
+  final List<AwaitExpression> awaits = [];
+  final List<ReturnStatement> returnStatements = [];
+
+  _FunctionData();
+}
+
+/// Handles simplification of basic 'async' functions into [Future]s.
+///
+/// The JS expansion of an async/await function is a complex state machine.
+/// In many cases 'async' functions either do not use 'await' or have very
+/// simple 'await' logic which is easily captured by [Future]. By making this
+/// transformation we avoid substantial over head from the state machine.
+class AsyncLowering {
+  final List<_FunctionData> _functions = [];
+  final CoreTypes _coreTypes;
+
+  AsyncLowering(this._coreTypes);
+
+  bool _shouldTryAsyncLowering(FunctionNode node) =>
+      node.asyncMarker == AsyncMarker.Async;
+
+  void enterFunction(FunctionNode node) {
+    _functions.add(_FunctionData());
+  }
+
+  void _exitFunction() {
+    _functions.removeLast();
+  }
+
+  void _updateFunctionBody(FunctionNode node, Statement? newBody) {
+    node.body = newBody;
+    newBody?.parent = node;
+  }
+
+  void _wrapBodySync(FunctionNode node) {
+    node.asyncMarker = AsyncMarker.Sync;
+    final futureValueType = node.futureValueType!;
+    _updateFunctionBody(
+        node,
+        ExpressionStatement(StaticInvocation(
+            _coreTypes.futureSyncFactory,
+            Arguments([
+              FunctionExpression(FunctionNode(node.body,
+                  returnType: FutureOrType(
+                      futureValueType, futureValueType.nullability)))
+            ], types: [
+              futureValueType
+            ]))));
+  }
+
+  void _transformAsyncFunctionNode(FunctionNode node) {
+    assert(_functions.isNotEmpty, 'Must be within a function scope.');
+    final functionData = _functions.last;
+    if (functionData.awaits.isEmpty) {
+      // There are no awaits within this function so convert to a simple
+      // Future.sync call with the function's returned expressions. We use
+      // this over Future.value because the expression can throw and async
+      // functions defer exceptions as errors on returned Future. Future.sync
+      // has the same deferred behavior.
+      //
+      // Before:
+      // Future<int> foo() async {
+      //   doSomething();
+      //   return 3;
+      // }
+      //
+      // After:
+      // Future<int> foo() {
+      //   return Future.sync(() {
+      //     doSomething();
+      //     return 3;
+      //   });
+      // }
+      //
+      // Edge cases to consider:
+      // 1) Function doesn't include a return expression. (e.g. Future<void>)
+      //    In this case we call Future.value(null).
+      // 2) The returned expression might itself be a future. Future.sync will
+      //    handle the unpacking of the returned future in that case.
+      // 3) The return type of the function is not specified. In this case we
+      //    instantiate Future.value with 'dynamic'.
+      _wrapBodySync(node);
+    }
+  }
+
+  void transformFunctionNodeAndExit(FunctionNode node) {
+    if (_shouldTryAsyncLowering(node)) _transformAsyncFunctionNode(node);
+    _exitFunction();
+  }
+
+  void visitAwaitExpression(AwaitExpression expression) {
+    assert(_functions.isNotEmpty,
+        'Awaits must be within the scope of a function.');
+    _functions.last.awaits.add(expression);
+  }
+
+  void visitReturnStatement(ReturnStatement statement) {
+    _functions.last.returnStatements.add(statement);
+  }
+}
diff --git a/pkg/compiler/lib/src/kernel/transformations/lowering.dart b/pkg/compiler/lib/src/kernel/transformations/lowering.dart
index 234eac1..ba289ae 100644
--- a/pkg/compiler/lib/src/kernel/transformations/lowering.dart
+++ b/pkg/compiler/lib/src/kernel/transformations/lowering.dart
@@ -9,6 +9,7 @@
 import 'package:kernel/core_types.dart' show CoreTypes;
 
 import '../../options.dart';
+import 'async_lowering.dart';
 import 'factory_specializer.dart';
 import 'late_lowering.dart';
 
@@ -26,13 +27,18 @@
 class _Lowering extends Transformer {
   final FactorySpecializer factorySpecializer;
   final LateLowering _lateLowering;
+  final AsyncLowering? _asyncLowering;
 
   Member? _currentMember;
 
   _Lowering(
       CoreTypes coreTypes, ClassHierarchy hierarchy, CompilerOptions? _options)
       : factorySpecializer = FactorySpecializer(coreTypes, hierarchy),
-        _lateLowering = LateLowering(coreTypes, _options);
+        _lateLowering = LateLowering(coreTypes, _options),
+        _asyncLowering =
+            (_options?.features.simpleAsyncToFuture.isEnabled ?? false)
+                ? AsyncLowering(coreTypes)
+                : null;
 
   @override
   TreeNode defaultMember(Member node) {
@@ -56,8 +62,10 @@
   @override
   TreeNode visitFunctionNode(FunctionNode node) {
     _lateLowering.enterFunction();
+    _asyncLowering?.enterFunction(node);
     node.transformChildren(this);
     _lateLowering.exitFunction();
+    _asyncLowering?.transformFunctionNodeAndExit(node);
     return node;
   }
 
@@ -85,4 +93,18 @@
     node.transformChildren(this);
     return _lateLowering.transformField(node, _currentMember!);
   }
+
+  @override
+  TreeNode visitAwaitExpression(AwaitExpression expression) {
+    _asyncLowering?.visitAwaitExpression(expression);
+    expression.transformChildren(this);
+    return expression;
+  }
+
+  @override
+  TreeNode visitReturnStatement(ReturnStatement statement) {
+    _asyncLowering?.visitReturnStatement(statement);
+    statement.transformChildren(this);
+    return statement;
+  }
 }
diff --git a/pkg/compiler/lib/src/options.dart b/pkg/compiler/lib/src/options.dart
index 554f833..23f019d 100644
--- a/pkg/compiler/lib/src/options.dart
+++ b/pkg/compiler/lib/src/options.dart
@@ -85,6 +85,10 @@
   /// dump-info's output.
   FeatureOption newDumpInfo = FeatureOption('new-dump-info');
 
+  /// Whether to implement some simple async functions using Futures directly
+  /// to reduce generated code size.
+  FeatureOption simpleAsyncToFuture = FeatureOption('simple-async-to-future');
+
   /// [FeatureOption]s which are shipped and cannot be toggled.
   late final List<FeatureOption> shipped = [
     newHolders,
@@ -97,7 +101,11 @@
   ];
 
   /// [FeatureOption]s which default to disabled.
-  late final List<FeatureOption> canary = [writeUtf8, newDumpInfo];
+  late final List<FeatureOption> canary = [
+    writeUtf8,
+    newDumpInfo,
+    simpleAsyncToFuture
+  ];
 
   /// Forces canary feature on. This must run after [Option].parse.
   void forceCanary() {
diff --git a/pkg/dart2wasm/lib/code_generator.dart b/pkg/dart2wasm/lib/code_generator.dart
index 6631460..b88ee02 100644
--- a/pkg/dart2wasm/lib/code_generator.dart
+++ b/pkg/dart2wasm/lib/code_generator.dart
@@ -2092,8 +2092,23 @@
 
   @override
   w.ValueType visitAsExpression(AsExpression node, w.ValueType expectedType) {
-    // TODO(joshualitt): Emit type test and throw exception on failure
-    return wrap(node.operand, expectedType);
+    w.Label asCheckBlock = b.block();
+    wrap(node.operand, translator.topInfo.nullableType);
+    w.Local operand = addLocal(translator.topInfo.nullableType);
+    b.local_tee(operand);
+
+    // We lower an `as` expression to a type test, throwing a [TypeError] if
+    // the type test fails.
+    emitTypeTest(node.type, dartTypeOf(node.operand), node);
+    b.br_if(asCheckBlock);
+    b.local_get(operand);
+    _makeType(node.type, node);
+    _call(translator.stackTraceCurrent.reference);
+    _call(translator.throwAsCheckError.reference);
+    b.unreachable();
+    b.end();
+    b.local_get(operand);
+    return operand.type;
   }
 }
 
diff --git a/pkg/dart2wasm/lib/translator.dart b/pkg/dart2wasm/lib/translator.dart
index 3bf91b4..1eacf81 100644
--- a/pkg/dart2wasm/lib/translator.dart
+++ b/pkg/dart2wasm/lib/translator.dart
@@ -95,6 +95,7 @@
   late final Procedure stringEquals;
   late final Procedure stringInterpolate;
   late final Procedure throwNullCheckError;
+  late final Procedure throwAsCheckError;
   late final Procedure mapFactory;
   late final Procedure mapPut;
   late final Procedure immutableMapIndexNullable;
@@ -192,6 +193,8 @@
         .firstWhere((p) => p.name.text == "_interpolate");
     throwNullCheckError = typeErrorClass.procedures
         .firstWhere((p) => p.name.text == "_throwNullCheckError");
+    throwAsCheckError = typeErrorClass.procedures
+        .firstWhere((p) => p.name.text == "_throwAsCheckError");
     mapFactory = lookupCollection("LinkedHashMap").procedures.firstWhere(
         (p) => p.kind == ProcedureKind.Factory && p.name.text == "_default");
     mapPut = lookupCollection("_CompactLinkedCustomHashMap")
diff --git a/pkg/dds/CHANGELOG.md b/pkg/dds/CHANGELOG.md
index 3cb8dd8..c0f272a 100644
--- a/pkg/dds/CHANGELOG.md
+++ b/pkg/dds/CHANGELOG.md
@@ -1,5 +1,8 @@
+# 2.2.1
+- Reduce latency of `streamListen` calls through improved locking behavior.
+
 # 2.2.0
-- Add support for serving DevTools via `package:dds/devtools_server.dart`
+- Add support for serving DevTools via `package:dds/devtools_server.dart`.
 
 # 2.1.7
 - Re-release 2.1.6+1.
diff --git a/pkg/dds/lib/src/stream_manager.dart b/pkg/dds/lib/src/stream_manager.dart
index cde830a..057d29d 100644
--- a/pkg/dds/lib/src/stream_manager.dart
+++ b/pkg/dds/lib/src/stream_manager.dart
@@ -173,7 +173,11 @@
     String stream, {
     bool? includePrivates,
   }) async {
-    await _streamSubscriptionMutex.runGuarded(
+    // Weakly guard stream listening as it's safe to perform multiple listens
+    // on a stream concurrently. However, cancelling streams while listening
+    // to them concurrently can put things in a bad state. Use weak guarding to
+    // improve latency of stream subscription.
+    await _streamSubscriptionMutex.runGuardedWeak(
       () async {
         assert(stream.isNotEmpty);
         bool streamNewlySubscribed = false;
diff --git a/pkg/dds/lib/src/utils/mutex.dart b/pkg/dds/lib/src/utils/mutex.dart
index b697e78..9aff468 100644
--- a/pkg/dds/lib/src/utils/mutex.dart
+++ b/pkg/dds/lib/src/utils/mutex.dart
@@ -11,9 +11,9 @@
   /// Executes a block of code containing asynchronous calls atomically.
   ///
   /// If no other asynchronous context is currently executing within
-  /// [criticalSection], it will immediately be called. Otherwise, the caller
-  /// will be suspended and entered into a queue to be resumed once the lock is
-  /// released.
+  /// [criticalSection] or a [runGuardedWeak] scope, it will immediately be
+  /// called. Otherwise, the caller will be suspended and entered into a queue
+  /// to be resumed once the lock is released.
   Future<T> runGuarded<T>(FutureOr<T> Function() criticalSection) async {
     try {
       await _acquireLock();
@@ -23,11 +23,49 @@
     }
   }
 
-  Future<void> _acquireLock() async {
+  /// Executes a block of code containing asynchronous calls, allowing for other
+  /// weakly guarded sections to be executed concurrently.
+  ///
+  /// If no other asynchronous context is currently executing within a
+  /// [runGuarded] scope, [criticalSection] will immediately be called.
+  /// Otherwise, the caller will be suspended and entered into a queue to be
+  /// resumed once the lock is released.
+  Future<T> runGuardedWeak<T>(FutureOr<T> Function() criticalSection) async {
+    _weakGuards++;
+    if (_weakGuards == 1) {
+      // Reinitialize if this is the only weakly guarded scope.
+      _outstandingReadersCompleter = Completer<void>();
+    }
+    final result;
+    try {
+      await _acquireLock(strong: false);
+      result = await criticalSection();
+    } finally {
+      _weakGuards--;
+      if (_weakGuards == 0) {
+        // Notify callers of `runGuarded` that they can try to execute again.
+        _outstandingReadersCompleter.complete();
+      }
+    }
+    return result;
+  }
+
+  Future<void> _acquireLock({bool strong = true}) async {
+    // The lock cannot be acquired by `runGuarded` if there is outstanding
+    // execution in weakly guarded sections. Loop in case we've entered another
+    // weakly guarded scope before we've woken up.
+    while (strong && _weakGuards > 0) {
+      await _outstandingReadersCompleter.future;
+    }
     if (!_locked) {
-      _locked = true;
+      if (strong) {
+        // Don't actually lock for weakly guarded sections, just make sure the
+        // lock isn't held before entering.
+        _locked = true;
+      }
       return;
     }
+
     final request = Completer<void>();
     _outstandingRequests.add(request);
     await request.future;
@@ -41,6 +79,8 @@
     }
   }
 
+  int _weakGuards = 0;
   bool _locked = false;
+  var _outstandingReadersCompleter = Completer<void>();
   final _outstandingRequests = Queue<Completer<void>>();
 }
diff --git a/pkg/dds/pubspec.yaml b/pkg/dds/pubspec.yaml
index 5ad059d..1430222 100644
--- a/pkg/dds/pubspec.yaml
+++ b/pkg/dds/pubspec.yaml
@@ -3,7 +3,7 @@
   A library used to spawn the Dart Developer Service, used to communicate with
   a Dart VM Service instance.
 
-version: 2.2.0
+version: 2.2.1
 
 homepage: https://github.com/dart-lang/sdk/tree/master/pkg/dds
 
diff --git a/pkg/dds/test/devtools_server/bind_test.dart b/pkg/dds/test/devtools_server/bind_test.dart
new file mode 100644
index 0000000..efb13bb
--- /dev/null
+++ b/pkg/dds/test/devtools_server/bind_test.dart
@@ -0,0 +1,51 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:test/test.dart';
+
+import 'utils/server_driver.dart';
+
+late final DevToolsServerTestController testController;
+
+void main() {
+  testController = DevToolsServerTestController();
+
+  setUp(() async {
+    await testController.setUp();
+  });
+
+  tearDown(() async {
+    await testController.tearDown();
+  });
+
+  test('can bind to next available port', () async {
+    final server1 = await DevToolsServerDriver.create(port: 8855);
+    try {
+      // Wait for the first server to start up and ensure it got the
+      // expected port.
+      final event1 = (await server1.stdout.firstWhere(
+        (map) => map!['event'] == 'server.started',
+      ))!;
+      expect(event1['params']['port'], 8855);
+
+      // Now spawn another requesting the same port and ensure it got the next
+      // port number.
+      final server2 = await DevToolsServerDriver.create(
+        port: 8855,
+        tryPorts: 2,
+      );
+      try {
+        final event2 = (await server2.stdout.firstWhere(
+          (map) => map!['event'] == 'server.started',
+        ))!;
+
+        expect(event2['params']['port'], 8856);
+      } finally {
+        server2.kill();
+      }
+    } finally {
+      server1.kill();
+    }
+  }, timeout: const Timeout.factor(10));
+}
diff --git a/pkg/dds/test/devtools_server/devtools_client_test.dart b/pkg/dds/test/devtools_server/client_test.dart
similarity index 100%
rename from pkg/dds/test/devtools_server/devtools_client_test.dart
rename to pkg/dds/test/devtools_server/client_test.dart
diff --git a/pkg/dds/test/devtools_server/devtools_server_connection_test.dart b/pkg/dds/test/devtools_server/devtools_server_connection_test.dart
deleted file mode 100644
index 5bb42a8..0000000
--- a/pkg/dds/test/devtools_server/devtools_server_connection_test.dart
+++ /dev/null
@@ -1,103 +0,0 @@
-// Copyright 2022 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'package:devtools_shared/devtools_test_utils.dart';
-import 'package:test/test.dart';
-
-import 'devtools_server_driver.dart';
-
-// Note: this test is broken out from devtools_server_test.dart so that the
-// tests run faster and we do not have to mark them as slow.
-
-late final DevToolsServerTestController testController;
-
-void main() {
-  testController = DevToolsServerTestController();
-
-  setUp(() async {
-    await testController.setUp();
-  });
-
-  tearDown(() async {
-    await testController.tearDown();
-  });
-
-  for (final bool useVmService in [true, false]) {
-    group('Server (${useVmService ? 'VM Service' : 'API'})', () {
-      test(
-          'DevTools connects back to server API and registers that it is connected',
-          () async {
-        // Register the VM.
-        await testController.send(
-          'vm.register',
-          {'uri': testController.appFixture.serviceUri.toString()},
-        );
-
-        // Send a request to launch DevTools in a browser.
-        await testController.sendLaunchDevToolsRequest(
-          useVmService: useVmService,
-        );
-
-        final serverResponse =
-            await testController.waitForClients(requiredConnectionState: true);
-        expect(serverResponse, isNotNull);
-        expect(serverResponse['clients'], hasLength(1));
-        expect(serverResponse['clients'][0]['hasConnection'], isTrue);
-        expect(
-          serverResponse['clients'][0]['vmServiceUri'],
-          testController.appFixture.serviceUri.toString(),
-        );
-      }, timeout: const Timeout.factor(10));
-
-      test('DevTools reports disconnects from a VM', () async {
-        // Register the VM.
-        await testController.send(
-          'vm.register',
-          {'uri': testController.appFixture.serviceUri.toString()},
-        );
-
-        // Send a request to launch DevTools in a browser.
-        await testController.sendLaunchDevToolsRequest(
-          useVmService: useVmService,
-        );
-
-        // Wait for the DevTools to inform server that it's connected.
-        await testController.waitForClients(requiredConnectionState: true);
-
-        // Terminate the VM.
-        await testController.appFixture.teardown();
-
-        // Ensure the client is marked as disconnected.
-        final serverResponse = await testController.waitForClients(
-          requiredConnectionState: false,
-        );
-        expect(serverResponse['clients'], hasLength(1));
-        expect(serverResponse['clients'][0]['hasConnection'], isFalse);
-        expect(serverResponse['clients'][0]['vmServiceUri'], isNull);
-      }, timeout: const Timeout.factor(20));
-
-      test('server removes clients that disconnect from the API', () async {
-        final event = await testController.serverStartedEvent.future;
-
-        // Spawn our own Chrome process so we can terminate it.
-        final devToolsUri =
-            'http://${event['params']['host']}:${event['params']['port']}';
-        final chrome = await Chrome.locate()!.start(url: devToolsUri);
-
-        // Wait for DevTools to inform server that it's connected.
-        await testController.waitForClients();
-
-        // Close the browser, which will disconnect DevTools SSE connection
-        // back to the server.
-        chrome.kill();
-
-        // Await a long delay to wait for the SSE client to close.
-        await delay(duration: const Duration(seconds: 15));
-
-        // Ensure the client is completely removed from the list.
-        await testController.waitForClients(expectNone: true);
-      }, timeout: const Timeout.factor(20));
-    });
-  }
-}
diff --git a/pkg/dds/test/devtools_server/devtools_server_test.dart b/pkg/dds/test/devtools_server/devtools_server_test.dart
deleted file mode 100644
index 2a0ce68..0000000
--- a/pkg/dds/test/devtools_server/devtools_server_test.dart
+++ /dev/null
@@ -1,369 +0,0 @@
-// Copyright 2022 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'dart:convert';
-import 'dart:io';
-
-import 'package:dds/devtools_server.dart';
-import 'package:dds/src/devtools/machine_mode_command_handler.dart';
-import 'package:devtools_shared/devtools_shared.dart';
-import 'package:devtools_shared/devtools_test_utils.dart';
-import 'package:test/test.dart';
-
-import 'devtools_server_driver.dart';
-
-late final DevToolsServerTestController testController;
-
-void main() {
-  testController = DevToolsServerTestController();
-
-  setUp(() async {
-    await testController.setUp();
-  });
-
-  tearDown(() async {
-    await testController.tearDown();
-  });
-
-  test('registers service', () async {
-    final serverResponse = await testController.send(
-      'vm.register',
-      {'uri': testController.appFixture.serviceUri.toString()},
-    );
-    expect(serverResponse['success'], isTrue);
-
-    // Expect the VM service to see the launchDevTools service registered.
-    expect(
-      testController.registeredServices,
-      contains(DevToolsServer.launchDevToolsService),
-    );
-  }, timeout: const Timeout.factor(10));
-
-  test('can bind to next available port', () async {
-    final server1 = await DevToolsServerDriver.create(port: 8855);
-    try {
-      // Wait for the first server to start up and ensure it got the
-      // expected port.
-      final event1 = (await server1.stdout.firstWhere(
-        (map) => map!['event'] == 'server.started',
-      ))!;
-      expect(event1['params']['port'], 8855);
-
-      // Now spawn another requesting the same port and ensure it got the next
-      // port number.
-      final server2 = await DevToolsServerDriver.create(
-        port: 8855,
-        tryPorts: 2,
-      );
-      try {
-        final event2 = (await server2.stdout.firstWhere(
-          (map) => map!['event'] == 'server.started',
-        ))!;
-
-        expect(event2['params']['port'], 8856);
-      } finally {
-        server2.kill();
-      }
-    } finally {
-      server1.kill();
-    }
-  }, timeout: const Timeout.factor(10));
-
-  test('allows embedding without flag', () async {
-    final server = await DevToolsServerDriver.create();
-    final httpClient = HttpClient();
-    late HttpClientResponse resp;
-    try {
-      final startedEvent = (await server.stdout.firstWhere(
-        (map) => map!['event'] == 'server.started',
-      ))!;
-      final host = startedEvent['params']['host'];
-      final port = startedEvent['params']['port'];
-
-      final req = await httpClient.get(host, port, '/');
-      resp = await req.close();
-      expect(resp.headers.value('x-frame-options'), isNull);
-    } finally {
-      httpClient.close();
-      await resp.drain();
-      server.kill();
-    }
-  }, timeout: const Timeout.factor(10));
-
-  test('does not allow embedding with flag', () async {
-    final server = await DevToolsServerDriver.create(
-      additionalArgs: ['--no-allow-embedding'],
-    );
-    final httpClient = HttpClient();
-    late HttpClientResponse resp;
-    try {
-      final startedEvent = (await server.stdout.firstWhere(
-        (map) => map!['event'] == 'server.started',
-      ))!;
-      final host = startedEvent['params']['host'];
-      final port = startedEvent['params']['port'];
-
-      final req = await httpClient.get(host, port, '/');
-      resp = await req.close();
-      expect(resp.headers.value('x-frame-options'), 'SAMEORIGIN');
-    } finally {
-      httpClient.close();
-      await resp.drain();
-      server.kill();
-    }
-  }, timeout: const Timeout.factor(10));
-
-  test('Analytics Survey', () async {
-    var serverResponse = await testController.send('devTools.survey', {
-      'surveyRequest': 'copyAndCreateDevToolsFile',
-    });
-    expect(serverResponse, isNotNull);
-    expect(serverResponse['success'], isTrue);
-
-    serverResponse = await testController.send('devTools.survey', {
-      'surveyRequest': apiSetActiveSurvey,
-      'value': 'Q3-2019',
-    });
-    expect(serverResponse, isNotNull);
-    expect(serverResponse['success'], isTrue);
-    expect(serverResponse['activeSurvey'], 'Q3-2019');
-
-    serverResponse = await testController.send('devTools.survey', {
-      'surveyRequest': apiIncrementSurveyShownCount,
-    });
-    expect(serverResponse, isNotNull);
-    expect(serverResponse['activeSurvey'], 'Q3-2019');
-    expect(serverResponse['surveyShownCount'], 1);
-
-    serverResponse = await testController.send('devTools.survey', {
-      'surveyRequest': apiIncrementSurveyShownCount,
-    });
-    expect(serverResponse, isNotNull);
-    expect(serverResponse['activeSurvey'], 'Q3-2019');
-    expect(serverResponse['surveyShownCount'], 2);
-
-    serverResponse = await testController.send('devTools.survey', {
-      'surveyRequest': apiGetSurveyShownCount,
-    });
-    expect(serverResponse, isNotNull);
-    expect(serverResponse['activeSurvey'], 'Q3-2019');
-    expect(serverResponse['surveyShownCount'], 2);
-
-    serverResponse = await testController.send('devTools.survey', {
-      'surveyRequest': apiGetSurveyActionTaken,
-    });
-    expect(serverResponse, isNotNull);
-    expect(serverResponse['activeSurvey'], 'Q3-2019');
-    expect(serverResponse['surveyActionTaken'], isFalse);
-
-    serverResponse = await testController.send('devTools.survey', {
-      'surveyRequest': apiSetSurveyActionTaken,
-      'value': json.encode(true),
-    });
-    expect(serverResponse, isNotNull);
-    expect(serverResponse['activeSurvey'], 'Q3-2019');
-    expect(serverResponse['surveyActionTaken'], isTrue);
-
-    serverResponse = await testController.send('devTools.survey', {
-      'surveyRequest': MachineModeCommandHandler.restoreDevToolsFile,
-    });
-    expect(serverResponse, isNotNull);
-    expect(serverResponse['success'], isTrue);
-    expect(
-      serverResponse['content'],
-      '{\n'
-      '  \"Q3-2019\": {\n'
-      '    \"surveyActionTaken\": true,\n'
-      '    \"surveyShownCount\": 2\n'
-      '  }\n'
-      '}\n',
-    );
-  }, timeout: const Timeout.factor(10));
-
-  for (final bool useVmService in [true, false]) {
-    group('Server (${useVmService ? 'VM Service' : 'API'})', () {
-      test('can launch on a specific page', () async {
-        // Register the VM.
-        await testController.send(
-          'vm.register',
-          {'uri': testController.appFixture.serviceUri.toString()},
-        );
-
-        // Send a request to launch at a certain page.
-        await testController.sendLaunchDevToolsRequest(
-          useVmService: useVmService,
-          page: 'memory',
-        );
-
-        final serverResponse =
-            await testController.waitForClients(requiredPage: 'memory');
-        expect(serverResponse, isNotNull);
-        expect(serverResponse['clients'], hasLength(1));
-        expect(serverResponse['clients'][0]['hasConnection'], isTrue);
-        expect(
-          serverResponse['clients'][0]['vmServiceUri'],
-          testController.appFixture.serviceUri.toString(),
-        );
-        expect(serverResponse['clients'][0]['currentPage'], 'memory');
-      }, timeout: const Timeout.factor(10));
-
-      test('can switch page', () async {
-        await testController.send(
-          'vm.register',
-          {'uri': testController.appFixture.serviceUri.toString()},
-        );
-
-        // Launch on the memory page and wait for the connection.
-        await testController.sendLaunchDevToolsRequest(
-          useVmService: useVmService,
-          page: 'memory',
-        );
-        await testController.waitForClients(requiredPage: 'memory');
-
-        // Re-launch, allowing reuse and with a different page.
-        await testController.sendLaunchDevToolsRequest(
-          useVmService: useVmService,
-          reuseWindows: true,
-          page: 'logging',
-        );
-
-        final serverResponse =
-            await testController.waitForClients(requiredPage: 'logging');
-        expect(serverResponse, isNotNull);
-        expect(serverResponse['clients'], hasLength(1));
-        expect(serverResponse['clients'][0]['hasConnection'], isTrue);
-        expect(
-          serverResponse['clients'][0]['vmServiceUri'],
-          testController.appFixture.serviceUri.toString(),
-        );
-        expect(serverResponse['clients'][0]['currentPage'], 'logging');
-      }, timeout: const Timeout.factor(20));
-
-      test('Server reuses DevTools instance if already connected to same VM',
-          () async {
-        // Register the VM.
-        await testController.send(
-          'vm.register',
-          {'uri': testController.appFixture.serviceUri.toString()},
-        );
-
-        // Send a request to launch DevTools in a browser.
-        await testController.sendLaunchDevToolsRequest(
-          useVmService: useVmService,
-        );
-
-        {
-          final serverResponse = await testController.waitForClients(
-            requiredConnectionState: true,
-          );
-          expect(serverResponse['clients'], hasLength(1));
-        }
-
-        // Request again, allowing reuse, and server emits an event saying the
-        // window was reused.
-        final launchResponse = await testController.sendLaunchDevToolsRequest(
-          useVmService: useVmService,
-          reuseWindows: true,
-        );
-        expect(launchResponse['reused'], isTrue);
-
-        // Ensure there's still only one connection (eg. we didn't spawn a new one
-        // we reused the existing one).
-        final serverResponse =
-            await testController.waitForClients(requiredConnectionState: true);
-        expect(serverResponse['clients'], hasLength(1));
-      }, timeout: const Timeout.factor(20));
-
-      test('Server does not reuse DevTools instance if embedded', () async {
-        // Register the VM.
-        await testController.send(
-          'vm.register',
-          {'uri': testController.appFixture.serviceUri.toString()},
-        );
-
-        // Spawn an embedded version of DevTools in a browser.
-        final event = await testController.serverStartedEvent.future;
-        final devToolsUri =
-            'http://${event['params']['host']}:${event['params']['port']}';
-        final launchUrl = '$devToolsUri/?embed=true&page=logging'
-            '&uri=${Uri.encodeQueryComponent(testController.appFixture.serviceUri.toString())}';
-        final chrome = await Chrome.locate()!.start(url: launchUrl);
-        try {
-          {
-            final serverResponse = await testController.waitForClients(
-              requiredConnectionState: true,
-            );
-            expect(serverResponse['clients'], hasLength(1));
-          }
-
-          // Send a request to the server to launch and ensure it did
-          // not reuse the existing connection. Launch it on a different page
-          // so we can easily tell once this one has connected.
-          final launchResponse = await testController.sendLaunchDevToolsRequest(
-            useVmService: useVmService,
-            reuseWindows: true,
-            page: 'memory',
-          );
-          expect(launchResponse['reused'], isFalse);
-
-          // Ensure there's now two connections.
-          final serverResponse = await testController.waitForClients(
-            requiredConnectionState: true,
-            requiredPage: 'memory',
-          );
-          expect(serverResponse['clients'], hasLength(2));
-        } finally {
-          chrome.kill();
-        }
-      }, timeout: const Timeout.factor(20));
-
-      test('Server reuses DevTools instance if not connected to a VM',
-          () async {
-        // Register the VM.
-        await testController.send(
-          'vm.register',
-          {'uri': testController.appFixture.serviceUri.toString()},
-        );
-
-        // Send a request to launch DevTools in a browser.
-        await testController.sendLaunchDevToolsRequest(
-          useVmService: useVmService,
-        );
-
-        // Wait for the DevTools to inform server that it's connected.
-        await testController.waitForClients(requiredConnectionState: true);
-
-        // Terminate the VM.
-        await testController.appFixture.teardown();
-
-        // Ensure the client is marked as disconnected.
-        await testController.waitForClients(requiredConnectionState: false);
-
-        // Start up a new app.
-        await testController.startApp();
-        await testController.send(
-          'vm.register',
-          {'uri': testController.appFixture.serviceUri.toString()},
-        );
-
-        // Send a new request to launch.
-        await testController.sendLaunchDevToolsRequest(
-          useVmService: useVmService,
-          reuseWindows: true,
-          notify: true,
-        );
-
-        // Ensure we now have a single connected client.
-        final serverResponse =
-            await testController.waitForClients(requiredConnectionState: true);
-        expect(serverResponse['clients'], hasLength(1));
-        expect(serverResponse['clients'][0]['hasConnection'], isTrue);
-        expect(
-          serverResponse['clients'][0]['vmServiceUri'],
-          testController.appFixture.serviceUri.toString(),
-        );
-      }, timeout: const Timeout.factor(20));
-    });
-  }
-}
diff --git a/pkg/dds/test/devtools_server/embedding_test.dart b/pkg/dds/test/devtools_server/embedding_test.dart
new file mode 100644
index 0000000..99adce4
--- /dev/null
+++ b/pkg/dds/test/devtools_server/embedding_test.dart
@@ -0,0 +1,44 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'dart:io';
+
+import 'package:test/test.dart';
+
+import 'utils/server_driver.dart';
+
+late final DevToolsServerTestController testController;
+
+void main() {
+  testController = DevToolsServerTestController();
+
+  setUp(() async {
+    await testController.setUp();
+  });
+
+  tearDown(() async {
+    await testController.tearDown();
+  });
+
+  test('allows embedding without flag', () async {
+    final server = await DevToolsServerDriver.create();
+    final httpClient = HttpClient();
+    late HttpClientResponse resp;
+    try {
+      final startedEvent = (await server.stdout.firstWhere(
+        (map) => map!['event'] == 'server.started',
+      ))!;
+      final host = startedEvent['params']['host'];
+      final port = startedEvent['params']['port'];
+
+      final req = await httpClient.get(host, port, '/');
+      resp = await req.close();
+      expect(resp.headers.value('x-frame-options'), isNull);
+    } finally {
+      httpClient.close();
+      await resp.drain();
+      server.kill();
+    }
+  }, timeout: const Timeout.factor(10));
+}
diff --git a/pkg/dds/test/fixtures/empty_dart_app.dart b/pkg/dds/test/devtools_server/fixtures/empty_dart_app.dart
similarity index 100%
rename from pkg/dds/test/fixtures/empty_dart_app.dart
rename to pkg/dds/test/devtools_server/fixtures/empty_dart_app.dart
diff --git a/pkg/dds/test/devtools_server/instance_reuse_test.dart b/pkg/dds/test/devtools_server/instance_reuse_test.dart
new file mode 100644
index 0000000..71d7428
--- /dev/null
+++ b/pkg/dds/test/devtools_server/instance_reuse_test.dart
@@ -0,0 +1,150 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:devtools_shared/devtools_test_utils.dart';
+import 'package:test/test.dart';
+
+import 'utils/server_driver.dart';
+
+late final DevToolsServerTestController testController;
+
+void main() {
+  testController = DevToolsServerTestController();
+
+  setUp(() async {
+    await testController.setUp();
+  });
+
+  tearDown(() async {
+    await testController.tearDown();
+  });
+
+  for (final bool useVmService in [true, false]) {
+    group('Server (${useVmService ? 'VM Service' : 'API'})', () {
+      test('reuses DevTools instance if already connected to same VM',
+          () async {
+        // Register the VM.
+        await testController.send(
+          'vm.register',
+          {'uri': testController.appFixture.serviceUri.toString()},
+        );
+
+        // Send a request to launch DevTools in a browser.
+        await testController.sendLaunchDevToolsRequest(
+          useVmService: useVmService,
+        );
+
+        {
+          final serverResponse = await testController.waitForClients(
+            requiredConnectionState: true,
+          );
+          expect(serverResponse['clients'], hasLength(1));
+        }
+
+        // Request again, allowing reuse, and server emits an event saying the
+        // window was reused.
+        final launchResponse = await testController.sendLaunchDevToolsRequest(
+          useVmService: useVmService,
+          reuseWindows: true,
+        );
+        expect(launchResponse['reused'], isTrue);
+
+        // Ensure there's still only one connection (eg. we didn't spawn a new one
+        // we reused the existing one).
+        final serverResponse =
+            await testController.waitForClients(requiredConnectionState: true);
+        expect(serverResponse['clients'], hasLength(1));
+      }, timeout: const Timeout.factor(20));
+
+      test('Server does not reuse DevTools instance if embedded', () async {
+        // Register the VM.
+        await testController.send(
+          'vm.register',
+          {'uri': testController.appFixture.serviceUri.toString()},
+        );
+
+        // Spawn an embedded version of DevTools in a browser.
+        final event = await testController.serverStartedEvent.future;
+        final devToolsUri =
+            'http://${event['params']['host']}:${event['params']['port']}';
+        final launchUrl = '$devToolsUri/?embed=true&page=logging'
+            '&uri=${Uri.encodeQueryComponent(testController.appFixture.serviceUri.toString())}';
+        final chrome = await Chrome.locate()!.start(url: launchUrl);
+        try {
+          {
+            final serverResponse = await testController.waitForClients(
+              requiredConnectionState: true,
+            );
+            expect(serverResponse['clients'], hasLength(1));
+          }
+
+          // Send a request to the server to launch and ensure it did
+          // not reuse the existing connection. Launch it on a different page
+          // so we can easily tell once this one has connected.
+          final launchResponse = await testController.sendLaunchDevToolsRequest(
+            useVmService: useVmService,
+            reuseWindows: true,
+            page: 'memory',
+          );
+          expect(launchResponse['reused'], isFalse);
+
+          // Ensure there's now two connections.
+          final serverResponse = await testController.waitForClients(
+            requiredConnectionState: true,
+            requiredPage: 'memory',
+          );
+          expect(serverResponse['clients'], hasLength(2));
+        } finally {
+          chrome.kill();
+        }
+      }, timeout: const Timeout.factor(20));
+
+      test('reuses DevTools instance if not connected to a VM', () async {
+        // Register the VM.
+        await testController.send(
+          'vm.register',
+          {'uri': testController.appFixture.serviceUri.toString()},
+        );
+
+        // Send a request to launch DevTools in a browser.
+        await testController.sendLaunchDevToolsRequest(
+          useVmService: useVmService,
+        );
+
+        // Wait for the DevTools to inform server that it's connected.
+        await testController.waitForClients(requiredConnectionState: true);
+
+        // Terminate the VM.
+        await testController.appFixture.teardown();
+
+        // Ensure the client is marked as disconnected.
+        await testController.waitForClients(requiredConnectionState: false);
+
+        // Start up a new app.
+        await testController.startApp();
+        await testController.send(
+          'vm.register',
+          {'uri': testController.appFixture.serviceUri.toString()},
+        );
+
+        // Send a new request to launch.
+        await testController.sendLaunchDevToolsRequest(
+          useVmService: useVmService,
+          reuseWindows: true,
+          notify: true,
+        );
+
+        // Ensure we now have a single connected client.
+        final serverResponse =
+            await testController.waitForClients(requiredConnectionState: true);
+        expect(serverResponse['clients'], hasLength(1));
+        expect(serverResponse['clients'][0]['hasConnection'], isTrue);
+        expect(
+          serverResponse['clients'][0]['vmServiceUri'],
+          testController.appFixture.serviceUri.toString(),
+        );
+      }, timeout: const Timeout.factor(20));
+    });
+  }
+}
diff --git a/pkg/dds/test/devtools_server/remote_control_test.dart b/pkg/dds/test/devtools_server/remote_control_test.dart
new file mode 100644
index 0000000..c93d2a0
--- /dev/null
+++ b/pkg/dds/test/devtools_server/remote_control_test.dart
@@ -0,0 +1,82 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:test/test.dart';
+
+import 'utils/server_driver.dart';
+
+late final DevToolsServerTestController testController;
+
+void main() {
+  testController = DevToolsServerTestController();
+
+  setUp(() async {
+    await testController.setUp();
+  });
+
+  tearDown(() async {
+    await testController.tearDown();
+  });
+
+  for (final bool useVmService in [true, false]) {
+    group('Server (${useVmService ? 'VM Service' : 'API'})', () {
+      test('can launch on a specific page', () async {
+        // Register the VM.
+        await testController.send(
+          'vm.register',
+          {'uri': testController.appFixture.serviceUri.toString()},
+        );
+
+        // Send a request to launch at a certain page.
+        await testController.sendLaunchDevToolsRequest(
+          useVmService: useVmService,
+          page: 'memory',
+        );
+
+        final serverResponse =
+            await testController.waitForClients(requiredPage: 'memory');
+        expect(serverResponse, isNotNull);
+        expect(serverResponse['clients'], hasLength(1));
+        expect(serverResponse['clients'][0]['hasConnection'], isTrue);
+        expect(
+          serverResponse['clients'][0]['vmServiceUri'],
+          testController.appFixture.serviceUri.toString(),
+        );
+        expect(serverResponse['clients'][0]['currentPage'], 'memory');
+      }, timeout: const Timeout.factor(10));
+
+      test('can switch page', () async {
+        await testController.send(
+          'vm.register',
+          {'uri': testController.appFixture.serviceUri.toString()},
+        );
+
+        // Launch on the memory page and wait for the connection.
+        await testController.sendLaunchDevToolsRequest(
+          useVmService: useVmService,
+          page: 'memory',
+        );
+        await testController.waitForClients(requiredPage: 'memory');
+
+        // Re-launch, allowing reuse and with a different page.
+        await testController.sendLaunchDevToolsRequest(
+          useVmService: useVmService,
+          reuseWindows: true,
+          page: 'logging',
+        );
+
+        final serverResponse =
+            await testController.waitForClients(requiredPage: 'logging');
+        expect(serverResponse, isNotNull);
+        expect(serverResponse['clients'], hasLength(1));
+        expect(serverResponse['clients'][0]['hasConnection'], isTrue);
+        expect(
+          serverResponse['clients'][0]['vmServiceUri'],
+          testController.appFixture.serviceUri.toString(),
+        );
+        expect(serverResponse['clients'][0]['currentPage'], 'logging');
+      }, timeout: const Timeout.factor(20));
+    });
+  }
+}
diff --git a/pkg/dds/test/devtools_server/server_connection_api_test.dart b/pkg/dds/test/devtools_server/server_connection_api_test.dart
new file mode 100644
index 0000000..e4d5ff8
--- /dev/null
+++ b/pkg/dds/test/devtools_server/server_connection_api_test.dart
@@ -0,0 +1,9 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'server_connection_common.dart';
+
+// This test reuses much of the same logic as server_connection_vm_service_test
+// but running both in the same process will result in a timeout.
+void main() => runTest(useVmService: false);
diff --git a/pkg/dds/test/devtools_server/server_connection_common.dart b/pkg/dds/test/devtools_server/server_connection_common.dart
new file mode 100644
index 0000000..f812e9a
--- /dev/null
+++ b/pkg/dds/test/devtools_server/server_connection_common.dart
@@ -0,0 +1,101 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:devtools_shared/devtools_test_utils.dart';
+import 'package:test/test.dart';
+
+import 'utils/server_driver.dart';
+
+// Note: this test is broken out from devtools_server_test.dart so that the
+// tests run faster and we do not have to mark them as slow.
+
+late final DevToolsServerTestController testController;
+
+void runTest({required bool useVmService}) {
+  testController = DevToolsServerTestController();
+
+  setUp(() async {
+    await testController.setUp();
+  });
+
+  tearDown(() async {
+    await testController.tearDown();
+  });
+
+  group('Server (${useVmService ? 'VM Service' : 'API'})', () {
+    test(
+        'DevTools connects back to server API and registers that it is connected',
+        () async {
+      // Register the VM.
+      await testController.send(
+        'vm.register',
+        {'uri': testController.appFixture.serviceUri.toString()},
+      );
+
+      // Send a request to launch DevTools in a browser.
+      await testController.sendLaunchDevToolsRequest(
+        useVmService: useVmService,
+      );
+
+      final serverResponse =
+          await testController.waitForClients(requiredConnectionState: true);
+      expect(serverResponse, isNotNull);
+      expect(serverResponse['clients'], hasLength(1));
+      expect(serverResponse['clients'][0]['hasConnection'], isTrue);
+      expect(
+        serverResponse['clients'][0]['vmServiceUri'],
+        testController.appFixture.serviceUri.toString(),
+      );
+    }, timeout: const Timeout.factor(10));
+
+    test('DevTools reports disconnects from a VM', () async {
+      // Register the VM.
+      await testController.send(
+        'vm.register',
+        {'uri': testController.appFixture.serviceUri.toString()},
+      );
+
+      // Send a request to launch DevTools in a browser.
+      await testController.sendLaunchDevToolsRequest(
+        useVmService: useVmService,
+      );
+
+      // Wait for the DevTools to inform server that it's connected.
+      await testController.waitForClients(requiredConnectionState: true);
+
+      // Terminate the VM.
+      await testController.appFixture.teardown();
+
+      // Ensure the client is marked as disconnected.
+      final serverResponse = await testController.waitForClients(
+        requiredConnectionState: false,
+      );
+      expect(serverResponse['clients'], hasLength(1));
+      expect(serverResponse['clients'][0]['hasConnection'], isFalse);
+      expect(serverResponse['clients'][0]['vmServiceUri'], isNull);
+    }, timeout: const Timeout.factor(20));
+
+    test('server removes clients that disconnect from the API', () async {
+      final event = await testController.serverStartedEvent.future;
+
+      // Spawn our own Chrome process so we can terminate it.
+      final devToolsUri =
+          'http://${event['params']['host']}:${event['params']['port']}';
+      final chrome = await Chrome.locate()!.start(url: devToolsUri);
+
+      // Wait for DevTools to inform server that it's connected.
+      await testController.waitForClients();
+
+      // Close the browser, which will disconnect DevTools SSE connection
+      // back to the server.
+      chrome.kill();
+
+      // Await a long delay to wait for the SSE client to close.
+      await delay(duration: const Duration(seconds: 15));
+
+      // Ensure the client is completely removed from the list.
+      await testController.waitForClients(expectNone: true);
+    }, timeout: const Timeout.factor(20));
+  });
+}
diff --git a/pkg/dds/test/devtools_server/server_connection_vm_service_test.dart b/pkg/dds/test/devtools_server/server_connection_vm_service_test.dart
new file mode 100644
index 0000000..ef4dfd3
--- /dev/null
+++ b/pkg/dds/test/devtools_server/server_connection_vm_service_test.dart
@@ -0,0 +1,9 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'server_connection_common.dart';
+
+// This test reuses much of the same logic as server_connection_api_test but
+// running both in the same process will result in a timeout.
+void main() => runTest(useVmService: true);
diff --git a/pkg/dds/test/devtools_server/service_registration_test.dart b/pkg/dds/test/devtools_server/service_registration_test.dart
new file mode 100644
index 0000000..fa85a4f
--- /dev/null
+++ b/pkg/dds/test/devtools_server/service_registration_test.dart
@@ -0,0 +1,36 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:dds/devtools_server.dart';
+import 'package:test/test.dart';
+
+import 'utils/server_driver.dart';
+
+late final DevToolsServerTestController testController;
+
+void main() {
+  testController = DevToolsServerTestController();
+
+  setUp(() async {
+    await testController.setUp();
+  });
+
+  tearDown(() async {
+    await testController.tearDown();
+  });
+
+  test('registers service', () async {
+    final serverResponse = await testController.send(
+      'vm.register',
+      {'uri': testController.appFixture.serviceUri.toString()},
+    );
+    expect(serverResponse['success'], isTrue);
+
+    // Expect the VM service to see the launchDevTools service registered.
+    expect(
+      testController.registeredServices,
+      contains(DevToolsServer.launchDevToolsService),
+    );
+  }, timeout: const Timeout.factor(10));
+}
diff --git a/pkg/dds/test/devtools_server/serve_devtools.dart b/pkg/dds/test/devtools_server/utils/serve_devtools.dart
similarity index 75%
rename from pkg/dds/test/devtools_server/serve_devtools.dart
rename to pkg/dds/test/devtools_server/utils/serve_devtools.dart
index 7fbd12c..ad0d764 100644
--- a/pkg/dds/test/devtools_server/serve_devtools.dart
+++ b/pkg/dds/test/devtools_server/utils/serve_devtools.dart
@@ -6,13 +6,14 @@
 
 import 'package:dds/devtools_server.dart';
 
-import '../common/test_helper.dart';
+import '../../common/test_helper.dart';
 
 void main(List<String> args) async {
   unawaited(
     DevToolsServer().serveDevToolsWithArgs(
       args,
-      customDevToolsPath: devtoolsAppUri(prefix: '../../../../').toFilePath(),
+      customDevToolsPath:
+          devtoolsAppUri(prefix: '../../../../../').toFilePath(),
     ),
   );
 }
diff --git a/pkg/dds/test/devtools_server/devtools_server_driver.dart b/pkg/dds/test/devtools_server/utils/server_driver.dart
similarity index 97%
rename from pkg/dds/test/devtools_server/devtools_server_driver.dart
rename to pkg/dds/test/devtools_server/utils/server_driver.dart
index 9f56b81..7a2d8c7 100644
--- a/pkg/dds/test/devtools_server/devtools_server_driver.dart
+++ b/pkg/dds/test/devtools_server/utils/server_driver.dart
@@ -63,8 +63,9 @@
     int? tryPorts,
     List<String> additionalArgs = const [],
   }) async {
-    final script =
-        Platform.script.resolveUri(Uri.parse('./serve_devtools.dart'));
+    final script = Platform.script.resolveUri(
+      Uri.parse('utils/serve_devtools.dart'),
+    );
     final args = [
       script.toFilePath(),
       '--machine',
@@ -200,8 +201,8 @@
   }
 
   Future<void> startApp() async {
-    final appUri = Platform.script
-        .resolveUri(Uri.parse('../fixtures/empty_dart_app.dart'));
+    final appUri =
+        Platform.script.resolveUri(Uri.parse('fixtures/empty_dart_app.dart'));
     appFixture = await CliAppFixture.create(appUri.toFilePath());
 
     // Track services method names as they're registered.
diff --git a/pkg/front_end/test/spell_checking_list_code.txt b/pkg/front_end/test/spell_checking_list_code.txt
index 099e6f8..80753e6 100644
--- a/pkg/front_end/test/spell_checking_list_code.txt
+++ b/pkg/front_end/test/spell_checking_list_code.txt
@@ -190,6 +190,7 @@
 ci
 circuited
 ck
+callbacks
 cl
 claim
 claimed
@@ -428,6 +429,7 @@
 established
 estimate
 eval
+exchanging
 execute
 executor
 executors
diff --git a/pkg/front_end/testcases/dart2js/async_lowering/disabled/basic_return.dart b/pkg/front_end/testcases/dart2js/async_lowering/disabled/basic_return.dart
new file mode 100644
index 0000000..81dfb3a
--- /dev/null
+++ b/pkg/front_end/testcases/dart2js/async_lowering/disabled/basic_return.dart
@@ -0,0 +1,8 @@
+Future<int> foo() async {
+  final c = 3;
+  return c;
+}
+
+void main() {
+  foo();
+}
diff --git a/pkg/front_end/testcases/dart2js/async_lowering/disabled/basic_return.dart.strong.expect b/pkg/front_end/testcases/dart2js/async_lowering/disabled/basic_return.dart.strong.expect
new file mode 100644
index 0000000..a9953eb
--- /dev/null
+++ b/pkg/front_end/testcases/dart2js/async_lowering/disabled/basic_return.dart.strong.expect
@@ -0,0 +1,12 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:async" as asy;
+import "dart:core" as core;
+
+static method foo() → asy::Future<core::int> async /* futureValueType= core::int */ {
+  final core::int c = 3;
+  return c;
+}
+static method main() → void {
+  self::foo();
+}
diff --git a/pkg/front_end/testcases/dart2js/async_lowering/disabled/basic_return.dart.strong.transformed.expect b/pkg/front_end/testcases/dart2js/async_lowering/disabled/basic_return.dart.strong.transformed.expect
new file mode 100644
index 0000000..a9953eb
--- /dev/null
+++ b/pkg/front_end/testcases/dart2js/async_lowering/disabled/basic_return.dart.strong.transformed.expect
@@ -0,0 +1,12 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:async" as asy;
+import "dart:core" as core;
+
+static method foo() → asy::Future<core::int> async /* futureValueType= core::int */ {
+  final core::int c = 3;
+  return c;
+}
+static method main() → void {
+  self::foo();
+}
diff --git a/pkg/front_end/testcases/dart2js/async_lowering/disabled/basic_return.dart.textual_outline.expect b/pkg/front_end/testcases/dart2js/async_lowering/disabled/basic_return.dart.textual_outline.expect
new file mode 100644
index 0000000..e611f16
--- /dev/null
+++ b/pkg/front_end/testcases/dart2js/async_lowering/disabled/basic_return.dart.textual_outline.expect
@@ -0,0 +1,2 @@
+Future<int> foo() async {}
+void main() {}
diff --git a/pkg/front_end/testcases/dart2js/async_lowering/disabled/basic_return.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/dart2js/async_lowering/disabled/basic_return.dart.textual_outline_modelled.expect
new file mode 100644
index 0000000..e611f16
--- /dev/null
+++ b/pkg/front_end/testcases/dart2js/async_lowering/disabled/basic_return.dart.textual_outline_modelled.expect
@@ -0,0 +1,2 @@
+Future<int> foo() async {}
+void main() {}
diff --git a/pkg/front_end/testcases/dart2js/async_lowering/disabled/basic_return.dart.weak.expect b/pkg/front_end/testcases/dart2js/async_lowering/disabled/basic_return.dart.weak.expect
new file mode 100644
index 0000000..a9953eb
--- /dev/null
+++ b/pkg/front_end/testcases/dart2js/async_lowering/disabled/basic_return.dart.weak.expect
@@ -0,0 +1,12 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:async" as asy;
+import "dart:core" as core;
+
+static method foo() → asy::Future<core::int> async /* futureValueType= core::int */ {
+  final core::int c = 3;
+  return c;
+}
+static method main() → void {
+  self::foo();
+}
diff --git a/pkg/front_end/testcases/dart2js/async_lowering/disabled/basic_return.dart.weak.modular.expect b/pkg/front_end/testcases/dart2js/async_lowering/disabled/basic_return.dart.weak.modular.expect
new file mode 100644
index 0000000..a9953eb
--- /dev/null
+++ b/pkg/front_end/testcases/dart2js/async_lowering/disabled/basic_return.dart.weak.modular.expect
@@ -0,0 +1,12 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:async" as asy;
+import "dart:core" as core;
+
+static method foo() → asy::Future<core::int> async /* futureValueType= core::int */ {
+  final core::int c = 3;
+  return c;
+}
+static method main() → void {
+  self::foo();
+}
diff --git a/pkg/front_end/testcases/dart2js/async_lowering/disabled/basic_return.dart.weak.outline.expect b/pkg/front_end/testcases/dart2js/async_lowering/disabled/basic_return.dart.weak.outline.expect
new file mode 100644
index 0000000..79880de
--- /dev/null
+++ b/pkg/front_end/testcases/dart2js/async_lowering/disabled/basic_return.dart.weak.outline.expect
@@ -0,0 +1,9 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:async" as asy;
+import "dart:core" as core;
+
+static method foo() → asy::Future<core::int> async 
+  ;
+static method main() → void
+  ;
diff --git a/pkg/front_end/testcases/dart2js/async_lowering/disabled/basic_return.dart.weak.transformed.expect b/pkg/front_end/testcases/dart2js/async_lowering/disabled/basic_return.dart.weak.transformed.expect
new file mode 100644
index 0000000..a9953eb
--- /dev/null
+++ b/pkg/front_end/testcases/dart2js/async_lowering/disabled/basic_return.dart.weak.transformed.expect
@@ -0,0 +1,12 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:async" as asy;
+import "dart:core" as core;
+
+static method foo() → asy::Future<core::int> async /* futureValueType= core::int */ {
+  final core::int c = 3;
+  return c;
+}
+static method main() → void {
+  self::foo();
+}
diff --git a/pkg/front_end/testcases/dart2js/async_lowering/disabled/folder.options b/pkg/front_end/testcases/dart2js/async_lowering/disabled/folder.options
new file mode 100644
index 0000000..636a0c8
--- /dev/null
+++ b/pkg/front_end/testcases/dart2js/async_lowering/disabled/folder.options
@@ -0,0 +1 @@
+--target=dart2js
diff --git a/pkg/front_end/testcases/dart2js/async_lowering/folder.options b/pkg/front_end/testcases/dart2js/async_lowering/folder.options
new file mode 100644
index 0000000..ee073f8
--- /dev/null
+++ b/pkg/front_end/testcases/dart2js/async_lowering/folder.options
@@ -0,0 +1,2 @@
+-D--simple-async-to-future
+--target=dart2js
diff --git a/pkg/front_end/testcases/dart2js/async_lowering/no_await.dart b/pkg/front_end/testcases/dart2js/async_lowering/no_await.dart
new file mode 100644
index 0000000..f8b5c92
--- /dev/null
+++ b/pkg/front_end/testcases/dart2js/async_lowering/no_await.dart
@@ -0,0 +1,40 @@
+// Wrap and return non-awaited expression.
+Future<int> foo1() async {
+  final c = 3;
+  return c;
+}
+
+// Add null Future return.
+Future<void> foo2() async {
+  final c = 3;
+}
+
+// Return dynamic Future when no type.
+foo3() async {
+  return 234;
+}
+
+void bar(Future<int> Function() func) {
+  func();
+}
+
+// Transform nested function even if parent is not convertible.
+Future<bool> foo4() async {
+  await Future.value(2);
+  bar(() async => 3);
+  return true;
+}
+
+// Convert multiple returns.
+Future<int> foo5(bool x) async {
+  if (x) return 123;
+  return 234;
+}
+
+void main() {
+  foo1();
+  foo2();
+  foo3();
+  foo4();
+  foo5(true);
+}
diff --git a/pkg/front_end/testcases/dart2js/async_lowering/no_await.dart.strong.expect b/pkg/front_end/testcases/dart2js/async_lowering/no_await.dart.strong.expect
new file mode 100644
index 0000000..b06980a
--- /dev/null
+++ b/pkg/front_end/testcases/dart2js/async_lowering/no_await.dart.strong.expect
@@ -0,0 +1,35 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:async" as asy;
+import "dart:core" as core;
+
+static method foo1() → asy::Future<core::int> async /* futureValueType= core::int */ {
+  final core::int c = 3;
+  return c;
+}
+static method foo2() → asy::Future<void> async /* futureValueType= void */ {
+  final core::int c = 3;
+}
+static method foo3() → dynamic async /* futureValueType= dynamic */ {
+  return 234;
+}
+static method bar(() → asy::Future<core::int> func) → void {
+  func(){() → asy::Future<core::int>};
+}
+static method foo4() → asy::Future<core::bool> async /* futureValueType= core::bool */ {
+  await asy::Future::value<core::int>(2);
+  self::bar(() → asy::Future<core::int> async /* futureValueType= core::int */ => 3);
+  return true;
+}
+static method foo5(core::bool x) → asy::Future<core::int> async /* futureValueType= core::int */ {
+  if(x)
+    return 123;
+  return 234;
+}
+static method main() → void {
+  self::foo1();
+  self::foo2();
+  self::foo3();
+  self::foo4();
+  self::foo5(true);
+}
diff --git a/pkg/front_end/testcases/dart2js/async_lowering/no_await.dart.strong.transformed.expect b/pkg/front_end/testcases/dart2js/async_lowering/no_await.dart.strong.transformed.expect
new file mode 100644
index 0000000..d8ebe4a
--- /dev/null
+++ b/pkg/front_end/testcases/dart2js/async_lowering/no_await.dart.strong.transformed.expect
@@ -0,0 +1,41 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:async" as asy;
+import "dart:core" as core;
+
+static method foo1() → asy::Future<core::int> /* futureValueType= core::int */ /* originally async */ 
+  asy::Future::sync<core::int>(() → FutureOr<core::int> {
+    final core::int c = 3;
+    return c;
+  });
+static method foo2() → asy::Future<void> /* futureValueType= void */ /* originally async */ 
+  asy::Future::sync<void>(() → FutureOr<void>? {
+    final core::int c = 3;
+  });
+static method foo3() → dynamic /* futureValueType= dynamic */ /* originally async */ 
+  asy::Future::sync<dynamic>(() → FutureOr<dynamic>? {
+    return 234;
+  });
+static method bar(() → asy::Future<core::int> func) → void {
+  func(){() → asy::Future<core::int>};
+}
+static method foo4() → asy::Future<core::bool> async /* futureValueType= core::bool */ {
+  await asy::Future::value<core::int>(2);
+  self::bar(() → asy::Future<core::int> /* futureValueType= core::int */ /* originally async */ 
+    asy::Future::sync<core::int>(() → FutureOr<core::int> => 3);
+);
+  return true;
+}
+static method foo5(core::bool x) → asy::Future<core::int> /* futureValueType= core::int */ /* originally async */ 
+  asy::Future::sync<core::int>(() → FutureOr<core::int> {
+    if(x)
+      return 123;
+    return 234;
+  });
+static method main() → void {
+  self::foo1();
+  self::foo2();
+  self::foo3();
+  self::foo4();
+  self::foo5(true);
+}
diff --git a/pkg/front_end/testcases/dart2js/async_lowering/no_await.dart.textual_outline.expect b/pkg/front_end/testcases/dart2js/async_lowering/no_await.dart.textual_outline.expect
new file mode 100644
index 0000000..0ed1446
--- /dev/null
+++ b/pkg/front_end/testcases/dart2js/async_lowering/no_await.dart.textual_outline.expect
@@ -0,0 +1,7 @@
+Future<int> foo1() async {}
+Future<void> foo2() async {}
+foo3() async {}
+void bar(Future<int> Function() func) {}
+Future<bool> foo4() async {}
+Future<int> foo5(bool x) async {}
+void main() {}
diff --git a/pkg/front_end/testcases/dart2js/async_lowering/no_await.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/dart2js/async_lowering/no_await.dart.textual_outline_modelled.expect
new file mode 100644
index 0000000..66be86c
--- /dev/null
+++ b/pkg/front_end/testcases/dart2js/async_lowering/no_await.dart.textual_outline_modelled.expect
@@ -0,0 +1,7 @@
+Future<bool> foo4() async {}
+Future<int> foo1() async {}
+Future<int> foo5(bool x) async {}
+Future<void> foo2() async {}
+foo3() async {}
+void bar(Future<int> Function() func) {}
+void main() {}
diff --git a/pkg/front_end/testcases/dart2js/async_lowering/no_await.dart.weak.expect b/pkg/front_end/testcases/dart2js/async_lowering/no_await.dart.weak.expect
new file mode 100644
index 0000000..b06980a
--- /dev/null
+++ b/pkg/front_end/testcases/dart2js/async_lowering/no_await.dart.weak.expect
@@ -0,0 +1,35 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:async" as asy;
+import "dart:core" as core;
+
+static method foo1() → asy::Future<core::int> async /* futureValueType= core::int */ {
+  final core::int c = 3;
+  return c;
+}
+static method foo2() → asy::Future<void> async /* futureValueType= void */ {
+  final core::int c = 3;
+}
+static method foo3() → dynamic async /* futureValueType= dynamic */ {
+  return 234;
+}
+static method bar(() → asy::Future<core::int> func) → void {
+  func(){() → asy::Future<core::int>};
+}
+static method foo4() → asy::Future<core::bool> async /* futureValueType= core::bool */ {
+  await asy::Future::value<core::int>(2);
+  self::bar(() → asy::Future<core::int> async /* futureValueType= core::int */ => 3);
+  return true;
+}
+static method foo5(core::bool x) → asy::Future<core::int> async /* futureValueType= core::int */ {
+  if(x)
+    return 123;
+  return 234;
+}
+static method main() → void {
+  self::foo1();
+  self::foo2();
+  self::foo3();
+  self::foo4();
+  self::foo5(true);
+}
diff --git a/pkg/front_end/testcases/dart2js/async_lowering/no_await.dart.weak.modular.expect b/pkg/front_end/testcases/dart2js/async_lowering/no_await.dart.weak.modular.expect
new file mode 100644
index 0000000..b06980a
--- /dev/null
+++ b/pkg/front_end/testcases/dart2js/async_lowering/no_await.dart.weak.modular.expect
@@ -0,0 +1,35 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:async" as asy;
+import "dart:core" as core;
+
+static method foo1() → asy::Future<core::int> async /* futureValueType= core::int */ {
+  final core::int c = 3;
+  return c;
+}
+static method foo2() → asy::Future<void> async /* futureValueType= void */ {
+  final core::int c = 3;
+}
+static method foo3() → dynamic async /* futureValueType= dynamic */ {
+  return 234;
+}
+static method bar(() → asy::Future<core::int> func) → void {
+  func(){() → asy::Future<core::int>};
+}
+static method foo4() → asy::Future<core::bool> async /* futureValueType= core::bool */ {
+  await asy::Future::value<core::int>(2);
+  self::bar(() → asy::Future<core::int> async /* futureValueType= core::int */ => 3);
+  return true;
+}
+static method foo5(core::bool x) → asy::Future<core::int> async /* futureValueType= core::int */ {
+  if(x)
+    return 123;
+  return 234;
+}
+static method main() → void {
+  self::foo1();
+  self::foo2();
+  self::foo3();
+  self::foo4();
+  self::foo5(true);
+}
diff --git a/pkg/front_end/testcases/dart2js/async_lowering/no_await.dart.weak.outline.expect b/pkg/front_end/testcases/dart2js/async_lowering/no_await.dart.weak.outline.expect
new file mode 100644
index 0000000..56f17c2
--- /dev/null
+++ b/pkg/front_end/testcases/dart2js/async_lowering/no_await.dart.weak.outline.expect
@@ -0,0 +1,19 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:async" as asy;
+import "dart:core" as core;
+
+static method foo1() → asy::Future<core::int> async 
+  ;
+static method foo2() → asy::Future<void> async 
+  ;
+static method foo3() → dynamic async 
+  ;
+static method bar(() → asy::Future<core::int> func) → void
+  ;
+static method foo4() → asy::Future<core::bool> async 
+  ;
+static method foo5(core::bool x) → asy::Future<core::int> async 
+  ;
+static method main() → void
+  ;
diff --git a/pkg/front_end/testcases/dart2js/async_lowering/no_await.dart.weak.transformed.expect b/pkg/front_end/testcases/dart2js/async_lowering/no_await.dart.weak.transformed.expect
new file mode 100644
index 0000000..d8ebe4a
--- /dev/null
+++ b/pkg/front_end/testcases/dart2js/async_lowering/no_await.dart.weak.transformed.expect
@@ -0,0 +1,41 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:async" as asy;
+import "dart:core" as core;
+
+static method foo1() → asy::Future<core::int> /* futureValueType= core::int */ /* originally async */ 
+  asy::Future::sync<core::int>(() → FutureOr<core::int> {
+    final core::int c = 3;
+    return c;
+  });
+static method foo2() → asy::Future<void> /* futureValueType= void */ /* originally async */ 
+  asy::Future::sync<void>(() → FutureOr<void>? {
+    final core::int c = 3;
+  });
+static method foo3() → dynamic /* futureValueType= dynamic */ /* originally async */ 
+  asy::Future::sync<dynamic>(() → FutureOr<dynamic>? {
+    return 234;
+  });
+static method bar(() → asy::Future<core::int> func) → void {
+  func(){() → asy::Future<core::int>};
+}
+static method foo4() → asy::Future<core::bool> async /* futureValueType= core::bool */ {
+  await asy::Future::value<core::int>(2);
+  self::bar(() → asy::Future<core::int> /* futureValueType= core::int */ /* originally async */ 
+    asy::Future::sync<core::int>(() → FutureOr<core::int> => 3);
+);
+  return true;
+}
+static method foo5(core::bool x) → asy::Future<core::int> /* futureValueType= core::int */ /* originally async */ 
+  asy::Future::sync<core::int>(() → FutureOr<core::int> {
+    if(x)
+      return 123;
+    return 234;
+  });
+static method main() → void {
+  self::foo1();
+  self::foo2();
+  self::foo3();
+  self::foo4();
+  self::foo5(true);
+}
diff --git a/pkg/front_end/testcases/dart2js/async_lowering/no_transform.dart b/pkg/front_end/testcases/dart2js/async_lowering/no_transform.dart
new file mode 100644
index 0000000..8581d4c
--- /dev/null
+++ b/pkg/front_end/testcases/dart2js/async_lowering/no_transform.dart
@@ -0,0 +1,12 @@
+Future<void> foo1() async {
+  await 6;
+}
+
+Future<int> foo2() async {
+  return await 6;
+}
+
+void main() {
+  foo1();
+  foo2();
+}
diff --git a/pkg/front_end/testcases/dart2js/async_lowering/no_transform.dart.strong.expect b/pkg/front_end/testcases/dart2js/async_lowering/no_transform.dart.strong.expect
new file mode 100644
index 0000000..10798bd8
--- /dev/null
+++ b/pkg/front_end/testcases/dart2js/async_lowering/no_transform.dart.strong.expect
@@ -0,0 +1,15 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:async" as asy;
+import "dart:core" as core;
+
+static method foo1() → asy::Future<void> async /* futureValueType= void */ {
+  await 6;
+}
+static method foo2() → asy::Future<core::int> async /* futureValueType= core::int */ {
+  return await 6;
+}
+static method main() → void {
+  self::foo1();
+  self::foo2();
+}
diff --git a/pkg/front_end/testcases/dart2js/async_lowering/no_transform.dart.strong.transformed.expect b/pkg/front_end/testcases/dart2js/async_lowering/no_transform.dart.strong.transformed.expect
new file mode 100644
index 0000000..10798bd8
--- /dev/null
+++ b/pkg/front_end/testcases/dart2js/async_lowering/no_transform.dart.strong.transformed.expect
@@ -0,0 +1,15 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:async" as asy;
+import "dart:core" as core;
+
+static method foo1() → asy::Future<void> async /* futureValueType= void */ {
+  await 6;
+}
+static method foo2() → asy::Future<core::int> async /* futureValueType= core::int */ {
+  return await 6;
+}
+static method main() → void {
+  self::foo1();
+  self::foo2();
+}
diff --git a/pkg/front_end/testcases/dart2js/async_lowering/no_transform.dart.textual_outline.expect b/pkg/front_end/testcases/dart2js/async_lowering/no_transform.dart.textual_outline.expect
new file mode 100644
index 0000000..06a5c0d
--- /dev/null
+++ b/pkg/front_end/testcases/dart2js/async_lowering/no_transform.dart.textual_outline.expect
@@ -0,0 +1,3 @@
+Future<void> foo1() async {}
+Future<int> foo2() async {}
+void main() {}
diff --git a/pkg/front_end/testcases/dart2js/async_lowering/no_transform.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/dart2js/async_lowering/no_transform.dart.textual_outline_modelled.expect
new file mode 100644
index 0000000..4ab99ee
--- /dev/null
+++ b/pkg/front_end/testcases/dart2js/async_lowering/no_transform.dart.textual_outline_modelled.expect
@@ -0,0 +1,3 @@
+Future<int> foo2() async {}
+Future<void> foo1() async {}
+void main() {}
diff --git a/pkg/front_end/testcases/dart2js/async_lowering/no_transform.dart.weak.expect b/pkg/front_end/testcases/dart2js/async_lowering/no_transform.dart.weak.expect
new file mode 100644
index 0000000..10798bd8
--- /dev/null
+++ b/pkg/front_end/testcases/dart2js/async_lowering/no_transform.dart.weak.expect
@@ -0,0 +1,15 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:async" as asy;
+import "dart:core" as core;
+
+static method foo1() → asy::Future<void> async /* futureValueType= void */ {
+  await 6;
+}
+static method foo2() → asy::Future<core::int> async /* futureValueType= core::int */ {
+  return await 6;
+}
+static method main() → void {
+  self::foo1();
+  self::foo2();
+}
diff --git a/pkg/front_end/testcases/dart2js/async_lowering/no_transform.dart.weak.modular.expect b/pkg/front_end/testcases/dart2js/async_lowering/no_transform.dart.weak.modular.expect
new file mode 100644
index 0000000..10798bd8
--- /dev/null
+++ b/pkg/front_end/testcases/dart2js/async_lowering/no_transform.dart.weak.modular.expect
@@ -0,0 +1,15 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:async" as asy;
+import "dart:core" as core;
+
+static method foo1() → asy::Future<void> async /* futureValueType= void */ {
+  await 6;
+}
+static method foo2() → asy::Future<core::int> async /* futureValueType= core::int */ {
+  return await 6;
+}
+static method main() → void {
+  self::foo1();
+  self::foo2();
+}
diff --git a/pkg/front_end/testcases/dart2js/async_lowering/no_transform.dart.weak.outline.expect b/pkg/front_end/testcases/dart2js/async_lowering/no_transform.dart.weak.outline.expect
new file mode 100644
index 0000000..85b14b9
--- /dev/null
+++ b/pkg/front_end/testcases/dart2js/async_lowering/no_transform.dart.weak.outline.expect
@@ -0,0 +1,11 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:async" as asy;
+import "dart:core" as core;
+
+static method foo1() → asy::Future<void> async 
+  ;
+static method foo2() → asy::Future<core::int> async 
+  ;
+static method main() → void
+  ;
diff --git a/pkg/front_end/testcases/dart2js/async_lowering/no_transform.dart.weak.transformed.expect b/pkg/front_end/testcases/dart2js/async_lowering/no_transform.dart.weak.transformed.expect
new file mode 100644
index 0000000..10798bd8
--- /dev/null
+++ b/pkg/front_end/testcases/dart2js/async_lowering/no_transform.dart.weak.transformed.expect
@@ -0,0 +1,15 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:async" as asy;
+import "dart:core" as core;
+
+static method foo1() → asy::Future<void> async /* futureValueType= void */ {
+  await 6;
+}
+static method foo2() → asy::Future<core::int> async /* futureValueType= core::int */ {
+  return await 6;
+}
+static method main() → void {
+  self::foo1();
+  self::foo2();
+}
diff --git a/pkg/kernel/lib/core_types.dart b/pkg/kernel/lib/core_types.dart
index 4dc523e..04f3cee 100644
--- a/pkg/kernel/lib/core_types.dart
+++ b/pkg/kernel/lib/core_types.dart
@@ -178,6 +178,9 @@
 
   late final Class futureClass = index.getClass('dart:core', 'Future');
 
+  late final Procedure futureSyncFactory =
+      index.getMember('dart:async', 'Future', 'sync') as Procedure;
+
   // TODO(dmitryas): Remove it when FutureOrType is fully supported.
   late final Class deprecatedFutureOrClass =
       index.getClass('dart:async', 'FutureOr');
diff --git a/pkg/pkg.status b/pkg/pkg.status
index 38658de..2aef55f 100644
--- a/pkg/pkg.status
+++ b/pkg/pkg.status
@@ -145,8 +145,6 @@
 vm_snapshot_analysis/test/*: SkipByDesign # Only meant to run on vm
 
 [ $system == windows ]
-dds/test/devtools_server/devtools_server_connection_test: Pass, Slow
-dds/test/devtools_server/devtools_server_test: Pass, Slow
 front_end/test/fasta/bootstrap_test: Skip # Issue 31902
 front_end/test/fasta/strong_test: Pass, Slow, Timeout
 front_end/test/fasta/text_serialization_test: Pass, Slow, Timeout
diff --git a/runtime/bin/secure_socket_utils.cc b/runtime/bin/secure_socket_utils.cc
index ad738e97..e9a742a 100644
--- a/runtime/bin/secure_socket_utils.cc
+++ b/runtime/bin/secure_socket_utils.cc
@@ -21,9 +21,13 @@
 
 // Get the error messages from BoringSSL, and put them in buffer as a
 // null-terminated string.
-void SecureSocketUtils::FetchErrorString(const SSL* ssl,
-                                         TextBuffer* text_buffer) {
+// This function extracts all the error messages into a string and returns
+// the first error code so that this error can be passed in as the OSError
+// error code to the IOException.
+uint32_t SecureSocketUtils::FetchErrorString(const SSL* ssl,
+                                             TextBuffer* text_buffer) {
   const char* sep = File::PathSeparator();
+  uint32_t errCode = 0;
   while (true) {
     const char* path = NULL;
     int line = -1;
@@ -31,6 +35,9 @@
     if (error == 0) {
       break;
     }
+    if (errCode == 0) {
+      errCode = error;
+    }
     text_buffer->Printf("\n\t%s", ERR_reason_error_string(error));
     if ((ssl != NULL) && (ERR_GET_LIB(error) == ERR_LIB_SSL) &&
         (ERR_GET_REASON(error) == SSL_R_CERTIFICATE_VERIFY_FAILED)) {
@@ -43,6 +50,7 @@
       text_buffer->Printf("(%s:%d)", path, line);
     }
   }
+  return errCode;
 }
 
 // Handle an error reported from the BoringSSL library.
@@ -53,7 +61,10 @@
   Dart_Handle exception;
   {
     TextBuffer error_string(SSL_ERROR_MESSAGE_BUFFER_SIZE);
-    SecureSocketUtils::FetchErrorString(ssl, &error_string);
+    uint32_t errCode = SecureSocketUtils::FetchErrorString(ssl, &error_string);
+    if (status == 0) {
+      status = errCode;
+    }
     OSError os_error_struct(status, error_string.buffer(), OSError::kBoringSSL);
     Dart_Handle os_error = DartUtils::NewDartOSError(&os_error_struct);
     exception =
diff --git a/runtime/bin/secure_socket_utils.h b/runtime/bin/secure_socket_utils.h
index e8e306e..bf1fb6b 100644
--- a/runtime/bin/secure_socket_utils.h
+++ b/runtime/bin/secure_socket_utils.h
@@ -47,7 +47,7 @@
            (ERR_GET_REASON(last_error) == PEM_R_NO_START_LINE);
   }
 
-  static void FetchErrorString(const SSL* ssl, TextBuffer* text_buffer);
+  static uint32_t FetchErrorString(const SSL* ssl, TextBuffer* text_buffer);
 };
 
 // Where the argument to the constructor is the handle for an object
diff --git a/runtime/bin/socket.cc b/runtime/bin/socket.cc
index 21bfadf..708bda4 100644
--- a/runtime/bin/socket.cc
+++ b/runtime/bin/socket.cc
@@ -914,7 +914,18 @@
       Socket::GetSocketIdNativeField(Dart_GetNativeArgument(args, 0));
   OSError os_error;
   SocketBase::GetError(socket->fd(), &os_error);
-  Dart_SetReturnValue(args, DartUtils::NewDartOSError(&os_error));
+  if (os_error.code() != 0) {
+    Dart_SetReturnValue(args, DartUtils::NewDartOSError(&os_error));
+  } else {
+    Dart_SetReturnValue(args, Dart_Null());
+  }
+}
+
+void FUNCTION_NAME(Socket_Fatal)(Dart_NativeArguments args) {
+  Dart_Handle msg = Dart_GetNativeArgument(args, 0);
+  const char* msgStr =
+      (!Dart_IsNull(msg)) ? DartUtils::GetStringValue(msg) : nullptr;
+  FATAL("Fatal error in dart:io (socket): %s", msgStr);
 }
 
 void FUNCTION_NAME(Socket_GetFD)(Dart_NativeArguments args) {
diff --git a/runtime/vm/compiler/stub_code_compiler_riscv.cc b/runtime/vm/compiler/stub_code_compiler_riscv.cc
index e63cc88..3b3715c 100644
--- a/runtime/vm/compiler/stub_code_compiler_riscv.cc
+++ b/runtime/vm/compiler/stub_code_compiler_riscv.cc
@@ -1675,18 +1675,17 @@
   __ sx(T4, Address(SP, 0 * target::kWordSize));
 
   // Atomically clear kOldAndNotRememberedBit.
-  // TODO(riscv): Use amoand instead of lr/sc.
   ASSERT(target::Object::tags_offset() == 0);
   __ subi(T3, A0, kHeapObjectTag);
-  // T3: Untagged address of header word (lr/sc do not support offsets).
-  Label retry;
-  __ Bind(&retry);
-  __ lr(T2, Address(T3, 0));
-  __ andi(TMP2, T2, 1 << target::UntaggedObject::kOldAndNotRememberedBit);
-  __ beqz(TMP2, &lost_race);
-  __ andi(T2, T2, ~(1 << target::UntaggedObject::kOldAndNotRememberedBit));
-  __ sc(T4, T2, Address(T3, 0));
-  __ bnez(T4, &retry);
+  // T3: Untagged address of header word (amo's do not support offsets).
+  __ li(TMP2, ~(1 << target::UntaggedObject::kOldAndNotRememberedBit));
+#if XLEN == 32
+  __ amoandw(TMP2, TMP2, Address(T3, 0));
+#else
+  __ amoandd(TMP2, TMP2, Address(T3, 0));
+#endif
+  __ andi(TMP2, TMP2, 1 << target::UntaggedObject::kOldAndNotRememberedBit);
+  __ beqz(TMP2, &lost_race);  // Was already clear -> lost race.
 
   // Load the StoreBuffer block out of the thread. Then load top_ out of the
   // StoreBufferBlock and add the address to the pointers_.
@@ -1738,18 +1737,18 @@
   __ sx(T4, Address(SP, 0 * target::kWordSize));
 
   // Atomically clear kOldAndNotMarkedBit.
-  // TODO(riscv): Use amoand instead of lr/sc.
-  Label marking_retry, marking_overflow;
+  Label marking_overflow;
   ASSERT(target::Object::tags_offset() == 0);
   __ subi(T3, A1, kHeapObjectTag);
-  // T3: Untagged address of header word (lr/sc do not support offsets).
-  __ Bind(&marking_retry);
-  __ lr(T2, Address(T3, 0));
-  __ andi(TMP2, T2, 1 << target::UntaggedObject::kOldAndNotMarkedBit);
-  __ beqz(TMP2, &lost_race);
-  __ andi(T2, T2, ~(1 << target::UntaggedObject::kOldAndNotMarkedBit));
-  __ sc(T4, T2, Address(T3, 0));
-  __ bnez(T4, &marking_retry);
+  // T3: Untagged address of header word (amo's do not support offsets).
+  __ li(TMP2, ~(1 << target::UntaggedObject::kOldAndNotMarkedBit));
+#if XLEN == 32
+  __ amoandw(TMP2, TMP2, Address(T3, 0));
+#else
+  __ amoandd(TMP2, TMP2, Address(T3, 0));
+#endif
+  __ andi(TMP2, TMP2, 1 << target::UntaggedObject::kOldAndNotMarkedBit);
+  __ beqz(TMP2, &lost_race);  // Was already clear -> lost race.
 
   __ LoadFromOffset(T4, THR, target::Thread::marking_stack_block_offset());
   __ LoadFromOffset(T2, T4, target::MarkingStackBlock::top_offset(),
diff --git a/sdk/lib/_internal/vm/bin/socket_patch.dart b/sdk/lib/_internal/vm/bin/socket_patch.dart
index 7c9925a..d0bddf8 100644
--- a/sdk/lib/_internal/vm/bin/socket_patch.dart
+++ b/sdk/lib/_internal/vm/bin/socket_patch.dart
@@ -877,8 +877,8 @@
         // the SO_ERROR option at level SOL_SOCKET to determine whether
         // connect() completed successfully (SO_ERROR is zero) or
         // unsuccessfully.
-        final OSError osError = socket.nativeGetError();
-        if (osError.errorCode != 0) {
+        final osError = socket.nativeGetError();
+        if (osError != null) {
           socket.close();
           error ??= osError;
           connectNext();
@@ -1404,8 +1404,12 @@
 
         if (i == errorEvent) {
           if (!isClosing) {
-            final err = nativeGetError();
-            reportError(err, null, err.message);
+            final osError = nativeGetError();
+            if (osError == null) {
+              _nativeFatal("Reporting error with OSError code of 0");
+            } else {
+              reportError(osError, null, osError.message);
+            }
           }
         } else if (!isClosed) {
           // If the connection is closed right after it's accepted, there's a
@@ -1688,7 +1692,7 @@
   @pragma("vm:external-name", "Socket_GetFD")
   external int get fd;
   @pragma("vm:external-name", "Socket_GetError")
-  external OSError nativeGetError();
+  external OSError? nativeGetError();
   @pragma("vm:external-name", "Socket_GetOption")
   external nativeGetOption(int option, int protocol);
   @pragma("vm:external-name", "Socket_GetRawOption")
@@ -1703,6 +1707,8 @@
   @pragma("vm:external-name", "Socket_LeaveMulticast")
   external void nativeLeaveMulticast(
       Uint8List addr, Uint8List? interfaceAddr, int interfaceIndex);
+  @pragma("vm:external-name", "Socket_Fatal")
+  external static void _nativeFatal(msg);
 }
 
 class _RawServerSocket extends Stream<RawSocket> implements RawServerSocket {
diff --git a/sdk/lib/_internal/wasm/lib/errors_patch.dart b/sdk/lib/_internal/wasm/lib/errors_patch.dart
index 810e550..d560ac0 100644
--- a/sdk/lib/_internal/wasm/lib/errors_patch.dart
+++ b/sdk/lib/_internal/wasm/lib/errors_patch.dart
@@ -56,4 +56,13 @@
         "Null check operator used on a null value", stackTrace);
     return _throwObjectWithStackTrace(typeError, stackTrace);
   }
+
+  @pragma("wasm:entry-point")
+  static Never _throwAsCheckError(
+      Object? operand, Type? type, StackTrace stackTrace) {
+    final typeError = _TypeError.fromMessageAndStackTrace(
+        "Type '${operand.runtimeType}' is not a subtype of type '$type' in type cast",
+        stackTrace);
+    return _throwObjectWithStackTrace(typeError, stackTrace);
+  }
 }
diff --git a/tests/standalone/io/secure_socket_error_test.dart b/tests/standalone/io/secure_socket_error_test.dart
new file mode 100644
index 0000000..270ea31
--- /dev/null
+++ b/tests/standalone/io/secure_socket_error_test.dart
@@ -0,0 +1,88 @@
+// Copyright (c) 2020, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+//
+// OtherResources=certificates/server_chain.pem
+// OtherResources=certificates/server_key.pem
+// OtherResources=certificates/trusted_certs.pem
+// OtherResources=certificates/server_chain.p12
+// OtherResources=certificates/server_key.p12
+// OtherResources=certificates/trusted_certs.p12
+
+import "package:async_helper/async_helper.dart";
+import "package:expect/expect.dart";
+import "package:path/path.dart";
+import "dart:async";
+import "dart:io";
+
+String localFile(path) => Platform.script.resolve(path).toFilePath();
+
+SecurityContext serverContext(String certType, String password) =>
+    new SecurityContext()
+      ..useCertificateChain(localFile('certificates/server_chain.$certType'),
+          password: password)
+      ..usePrivateKey(localFile('certificates/server_key.$certType'),
+          password: password);
+
+SecurityContext clientContext(String certType, String password) =>
+    new SecurityContext()
+      ..setTrustedCertificates(
+          localFile('certificates/trusted_certs.$certType'),
+          password: password);
+
+Future<HttpServer> startServer(String certType, String password) {
+  return HttpServer.bindSecure(
+          "localhost", 0, serverContext(certType, password),
+          backlog: 5)
+      .then((server) {
+    server.listen((HttpRequest request) {
+      request.listen((_) {}, onDone: () {
+        request.response.contentLength = 100;
+        for (int i = 0; i < 10; i++) {
+          request.response.add([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
+        }
+        request.response.close();
+      });
+    });
+    return server;
+  });
+}
+
+Future test(String certType, String password) {
+  List<int> body = <int>[];
+  Completer completer = new Completer();
+  startServer(certType, password).then((server) {
+    try {
+      SecureSocket.connect("localhost", server.port,
+              context: clientContext(certType, 'junkjunk'))
+          .then((socket) {
+        socket.write("GET / HTTP/1.0\r\nHost: localhost\r\n\r\n");
+        socket.close();
+        socket.listen((List<int> data) {
+          body.addAll(data);
+        }, onDone: () {
+          server.close();
+          completer.complete(null);
+        }, onError: (e, trace) {
+          server.close();
+          completer.complete(null);
+        });
+      });
+    } catch (e) {
+      Expect.isTrue(e is TlsException);
+      var err = (e as TlsException).osError;
+      Expect.isTrue(err is OSError);
+      Expect.isTrue(err!.errorCode != 0);
+      server.close();
+      completer.complete(null);
+    }
+  });
+  return completer.future;
+}
+
+main() async {
+  asyncStart();
+  await test('pem', 'dartdart');
+  await test('p12', 'dartdart');
+  asyncEnd();
+}
diff --git a/tests/standalone_2/io/secure_socket_error_test.dart b/tests/standalone_2/io/secure_socket_error_test.dart
new file mode 100644
index 0000000..270ea31
--- /dev/null
+++ b/tests/standalone_2/io/secure_socket_error_test.dart
@@ -0,0 +1,88 @@
+// Copyright (c) 2020, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+//
+// OtherResources=certificates/server_chain.pem
+// OtherResources=certificates/server_key.pem
+// OtherResources=certificates/trusted_certs.pem
+// OtherResources=certificates/server_chain.p12
+// OtherResources=certificates/server_key.p12
+// OtherResources=certificates/trusted_certs.p12
+
+import "package:async_helper/async_helper.dart";
+import "package:expect/expect.dart";
+import "package:path/path.dart";
+import "dart:async";
+import "dart:io";
+
+String localFile(path) => Platform.script.resolve(path).toFilePath();
+
+SecurityContext serverContext(String certType, String password) =>
+    new SecurityContext()
+      ..useCertificateChain(localFile('certificates/server_chain.$certType'),
+          password: password)
+      ..usePrivateKey(localFile('certificates/server_key.$certType'),
+          password: password);
+
+SecurityContext clientContext(String certType, String password) =>
+    new SecurityContext()
+      ..setTrustedCertificates(
+          localFile('certificates/trusted_certs.$certType'),
+          password: password);
+
+Future<HttpServer> startServer(String certType, String password) {
+  return HttpServer.bindSecure(
+          "localhost", 0, serverContext(certType, password),
+          backlog: 5)
+      .then((server) {
+    server.listen((HttpRequest request) {
+      request.listen((_) {}, onDone: () {
+        request.response.contentLength = 100;
+        for (int i = 0; i < 10; i++) {
+          request.response.add([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
+        }
+        request.response.close();
+      });
+    });
+    return server;
+  });
+}
+
+Future test(String certType, String password) {
+  List<int> body = <int>[];
+  Completer completer = new Completer();
+  startServer(certType, password).then((server) {
+    try {
+      SecureSocket.connect("localhost", server.port,
+              context: clientContext(certType, 'junkjunk'))
+          .then((socket) {
+        socket.write("GET / HTTP/1.0\r\nHost: localhost\r\n\r\n");
+        socket.close();
+        socket.listen((List<int> data) {
+          body.addAll(data);
+        }, onDone: () {
+          server.close();
+          completer.complete(null);
+        }, onError: (e, trace) {
+          server.close();
+          completer.complete(null);
+        });
+      });
+    } catch (e) {
+      Expect.isTrue(e is TlsException);
+      var err = (e as TlsException).osError;
+      Expect.isTrue(err is OSError);
+      Expect.isTrue(err!.errorCode != 0);
+      server.close();
+      completer.complete(null);
+    }
+  });
+  return completer.future;
+}
+
+main() async {
+  asyncStart();
+  await test('pem', 'dartdart');
+  await test('p12', 'dartdart');
+  asyncEnd();
+}
diff --git a/tools/VERSION b/tools/VERSION
index 858518f..4bd1333 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 17
 PATCH 0
-PRERELEASE 271
+PRERELEASE 272
 PRERELEASE_PATCH 0
\ No newline at end of file