[ package:dds ] Silently handle exceptions raised within RPC request
handlers

Fixes https://github.com/flutter/flutter/issues/84113

Change-Id: I416308845c5c7f9a1bb547b6429f1e9d49393583
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/212268
Reviewed-by: Zach Anderson <zra@google.com>
Commit-Queue: Ben Konyi <bkonyi@google.com>
diff --git a/pkg/dds/CHANGELOG.md b/pkg/dds/CHANGELOG.md
index 7813107..953e36b 100644
--- a/pkg/dds/CHANGELOG.md
+++ b/pkg/dds/CHANGELOG.md
@@ -1,3 +1,6 @@
+# 2.1.2
+- Silently handle exceptions that occur within RPC request handlers.
+
 # 2.1.1
 - Fix another possibility of `LateInitializationError` being thrown when trying to
   cleanup after an error during initialization.
diff --git a/pkg/dds/lib/src/dds_impl.dart b/pkg/dds/lib/src/dds_impl.dart
index b0a2a50..7de1b71 100644
--- a/pkg/dds/lib/src/dds_impl.dart
+++ b/pkg/dds/lib/src/dds_impl.dart
@@ -149,8 +149,17 @@
     }
     pipeline = pipeline.addMiddleware(_authCodeMiddleware);
     final handler = pipeline.addHandler(_handlers().handler);
-    // Start the DDS server.
-    _server = await io.serve(handler, host, port);
+    // Start the DDS server. Run in an error Zone to ensure that asynchronous
+    // exceptions encountered during request handling are handled, as exceptions
+    // thrown during request handling shouldn't take down the entire service.
+    _server = await runZonedGuarded(
+      () async => await io.serve(handler, host, port),
+      (error, stack) {
+        if (shouldLogRequests) {
+          print('Asynchronous error: $error\n$stack');
+        }
+      },
+    )!;
 
     final tmpUri = Uri(
       scheme: 'http',
diff --git a/pkg/dds/pubspec.yaml b/pkg/dds/pubspec.yaml
index c11e0f2..ae212ba 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.1.1
+version: 2.1.2
 
 homepage: https://github.com/dart-lang/sdk/tree/master/pkg/dds
 
diff --git a/pkg/dds/test/handles_exceptions_in_shelf_handlers.dart b/pkg/dds/test/handles_exceptions_in_shelf_handlers.dart
new file mode 100644
index 0000000..dda9ebd
--- /dev/null
+++ b/pkg/dds/test/handles_exceptions_in_shelf_handlers.dart
@@ -0,0 +1,54 @@
+// Copyright (c) 2021, 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 'package:dds/dds.dart';
+import 'package:dds/src/dds_impl.dart';
+import 'package:test/test.dart';
+import 'package:web_socket_channel/web_socket_channel.dart';
+
+import 'common/fakes.dart';
+
+Future<HttpServer> startHttpServer() async {
+  final server = await HttpServer.bind(InternetAddress.loopbackIPv4, 0);
+  server.listen((event) async {
+    event.response.add([1, 2, 3]);
+    await event.response.flush();
+    await server.close(force: true);
+  });
+  return server;
+}
+
+void main() {
+  webSocketBuilder = (Uri _) => FakeWebSocketChannel();
+  peerBuilder = (WebSocketChannel _, dynamic __) async => FakePeer();
+
+  test("Handles 'Connection closed before full header was received'", () async {
+    final httpServer = await startHttpServer();
+    final dds = await DartDevelopmentService.startDartDevelopmentService(
+      Uri(scheme: 'http', host: httpServer.address.host, port: httpServer.port),
+      enableAuthCodes: false,
+    );
+    final uri = dds.uri!;
+
+    try {
+      final client = HttpClient();
+      final request = await client.get(uri.host, uri.port, 'getVM');
+      await request.close();
+      fail('Unexpected successful response');
+    } catch (e) {
+      expect(
+        e.toString(),
+        contains(
+          'Connection closed before full header was received',
+        ),
+      );
+    } finally {
+      await dds.shutdown();
+      await dds.done;
+    }
+  });
+}