Recover from used port errors when starting debug service (#1379)

Closes: https://github.com/dart-lang/webdev/issues/1378
diff --git a/dwds/CHANGELOG.md b/dwds/CHANGELOG.md
index 6999744..4952058 100644
--- a/dwds/CHANGELOG.md
+++ b/dwds/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 11.2.1-dev
+
+- Recover from used port errors when starting debug service.
+
 ## 11.2.0
 
 - Throw `SentinelException` instead of `RPCError` on vm service
diff --git a/dwds/lib/src/services/debug_service.dart b/dwds/lib/src/services/debug_service.dart
index 066afe3..f9c4819 100644
--- a/dwds/lib/src/services/debug_service.dart
+++ b/dwds/lib/src/services/debug_service.dart
@@ -11,7 +11,6 @@
 import 'dart:typed_data';
 
 import 'package:dds/dds.dart';
-import 'package:http_multi_server/http_multi_server.dart';
 import 'package:pedantic/pedantic.dart';
 import 'package:shelf/shelf.dart' as shelf;
 import 'package:shelf/shelf.dart' hide Response;
@@ -253,13 +252,12 @@
         return innerHandler(request);
       };
     }
-    var port = await findUnusedPort();
-    var server = await HttpMultiServer.bind(hostname, port);
+    var server = await startHttpServer(hostname);
     serveRequests(server, handler);
     return DebugService._(
       chromeProxyService,
       server.address.host,
-      port,
+      server.port,
       authToken,
       serviceExtensionRegistry,
       server,
diff --git a/dwds/lib/src/utilities/shared.dart b/dwds/lib/src/utilities/shared.dart
index 52ab563..a485f70 100644
--- a/dwds/lib/src/utilities/shared.dart
+++ b/dwds/lib/src/utilities/shared.dart
@@ -6,6 +6,7 @@
 
 import 'dart:io';
 
+import 'package:http_multi_server/http_multi_server.dart';
 import 'package:vm_service/vm_service.dart';
 import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart';
 
@@ -47,6 +48,28 @@
   return port;
 }
 
+/// Finds unused port and binds a new http server to it.
+///
+/// Retries a few times to recover from errors due to
+/// another thread or process opening the same port.
+Future<HttpServer> startHttpServer(String hostname, {int port}) async {
+  HttpServer httpServer;
+  var retries = 5;
+  var i = 0;
+  while (i < retries) {
+    i++;
+    try {
+      httpServer =
+          await HttpMultiServer.bind(hostname, port ?? await findUnusedPort());
+    } on SocketException {
+      if (i == retries) rethrow;
+    }
+    if (httpServer != null || i == retries) return httpServer;
+    await Future<void>.delayed(const Duration(milliseconds: 100));
+  }
+  return httpServer;
+}
+
 /// Throws an [ExceptionDetails] object if `exceptionDetails` is present on the
 /// result.
 void handleErrorIfPresent(WipResponse response,
diff --git a/dwds/lib/src/version.dart b/dwds/lib/src/version.dart
index cbd4af3..b419d06 100644
--- a/dwds/lib/src/version.dart
+++ b/dwds/lib/src/version.dart
@@ -1,2 +1,2 @@
 // Generated code. Do not modify.
-const packageVersion = '11.2.0';
+const packageVersion = '11.2.1-dev';
diff --git a/dwds/pubspec.yaml b/dwds/pubspec.yaml
index 5665cce..013dcd4 100644
--- a/dwds/pubspec.yaml
+++ b/dwds/pubspec.yaml
@@ -1,6 +1,6 @@
 name: dwds
 # Every time this changes you need to run `pub run build_runner build`.
-version: 11.2.0
+version: 11.2.1-dev
 homepage: https://github.com/dart-lang/webdev/tree/master/dwds
 description: >-
   A service that proxies between the Chrome debug protocol and the Dart VM
diff --git a/dwds/test/expression_compiler_service_test.dart b/dwds/test/expression_compiler_service_test.dart
index 665bcdb..49124b7 100644
--- a/dwds/test/expression_compiler_service_test.dart
+++ b/dwds/test/expression_compiler_service_test.dart
@@ -10,7 +10,6 @@
 
 import 'package:dwds/dwds.dart';
 import 'package:dwds/src/utilities/shared.dart';
-import 'package:http_multi_server/http_multi_server.dart';
 import 'package:path/path.dart' as p;
 import 'package:shelf/shelf.dart';
 import 'package:shelf/shelf_io.dart' as shelf_io;
@@ -53,8 +52,11 @@
           customLogWriter: (level, message, {error, loggerName, stackTrace}) =>
               output.add('[$level] $loggerName: $message'));
 
+      // start asset server
+      server = await startHttpServer('localhost');
+      var port = server.port;
+
       // start expression compilation service
-      final port = await findUnusedPort();
       final assetHandler = (request) =>
           Response(200, body: File.fromUri(kernel).readAsBytesSync());
       service =
@@ -63,7 +65,6 @@
       await service.initialize(moduleFormat: 'amd');
 
       // setup asset server
-      server = await HttpMultiServer.bind('localhost', port);
       shelf_io.serveRequests(
           server, Cascade().add(service.handler).add(assetHandler).handler);