Swallow errors in subscriptions in http_multi_server..

BUG=19815
R=alanknight@google.com

Review URL: https://codereview.chromium.org//411203002

git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@38532 260f80e4-7a28-3924-810f-c04153c831b5
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..ac00908
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,7 @@
+## 1.0.1
+
+* Ignore errors from one of the servers if others are still bound. In
+  particular, this works around [issue 19815][] on some Windows machines where
+  IPv6 failure isn't discovered until we try to connect to the socket.
+
+[issue 19815]: http://code.google.com/p/dart/issues/detail?id=19815
diff --git a/lib/http_multi_server.dart b/lib/http_multi_server.dart
index c055367..da12888 100644
--- a/lib/http_multi_server.dart
+++ b/lib/http_multi_server.dart
@@ -99,20 +99,15 @@
   /// [HttpServer.bindSecure].
   static Future<HttpServer> _loopback(int port,
       Future<HttpServer> bind(InternetAddress address, int port)) {
-    return Future.wait([
-      supportsIpV6,
-      bind(InternetAddress.LOOPBACK_IP_V4, port)
-    ]).then((results) {
-      var supportsIpV6 = results[0];
-      var v4Server = results[1];
-
-      if (!supportsIpV6) return v4Server;
-
+    return bind(InternetAddress.LOOPBACK_IP_V4, port).then((v4Server) {
       // Reuse the IPv4 server's port so that if [port] is 0, both servers use
       // the same ephemeral port.
       return bind(InternetAddress.LOOPBACK_IP_V6, v4Server.port)
-          .then((v6Server) {
-        return new HttpMultiServer([v4Server, v6Server]);
+          .then((v6Server) => new HttpMultiServer([v4Server, v6Server]))
+          .catchError((error) {
+        // If we fail to bind to IPv6, just use IPv4.
+        if (error is SocketException) return v4Server;
+        throw error;
       });
     });
   }
diff --git a/lib/src/utils.dart b/lib/src/utils.dart
index 9e95aba..d615975 100644
--- a/lib/src/utils.dart
+++ b/lib/src/utils.dart
@@ -7,6 +7,8 @@
 import 'dart:async';
 import 'dart:io';
 
+// TODO(nweiz): Revert this to the version of [mergeStreams] found elsewhere in
+// the repo once issue 19815 is fixed in dart:io.
 /// Merges all streams in [streams] into a single stream that emits all of their
 /// values.
 ///
@@ -18,9 +20,17 @@
   controller = new StreamController(onListen: () {
     for (var stream in streams) {
       var subscription;
-      subscription = stream.listen(controller.add,
-          onError: controller.addError,
-          onDone: () {
+      subscription = stream.listen(controller.add, onError: (error, trace) {
+        if (subscriptions.length == 1) {
+          // If the last subscription errored, pass it on.
+          controller.addError(error, trace);
+        } else {
+          // If only one of the subscriptions has an error (usually IPv6 failing
+          // late), then just remove that subscription and ignore the error.
+          subscriptions.remove(subscription);
+          subscription.cancel();
+        }
+      }, onDone: () {
         subscriptions.remove(subscription);
         if (subscriptions.isEmpty) controller.close();
       });
@@ -42,21 +52,3 @@
 
   return controller.stream;
 }
-
-/// A cache for [supportsIpV6].
-bool _supportsIpV6;
-
-/// Returns whether this computer supports binding to IPv6 addresses.
-Future<bool> get supportsIpV6 {
-  if (_supportsIpV6 != null) return new Future.value(_supportsIpV6);
-
-  return ServerSocket.bind(InternetAddress.LOOPBACK_IP_V6, 0).then((socket) {
-    _supportsIpV6 = true;
-    socket.close();
-    return true;
-  }).catchError((error) {
-    if (error is! SocketException) throw error;
-    _supportsIpV6 = false;
-    return false;
-  });
-}
diff --git a/pubspec.yaml b/pubspec.yaml
index 4199c58..2bdb4a2 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,5 +1,5 @@
 name: http_multi_server
-version: 1.0.0
+version: 1.0.1
 author: "Dart Team <misc@dartlang.org>"
 homepage: http://www.dartlang.org
 description:
diff --git a/test/http_multi_server_test.dart b/test/http_multi_server_test.dart
index a3d9062..0140ee7 100644
--- a/test/http_multi_server_test.dart
+++ b/test/http_multi_server_test.dart
@@ -108,7 +108,7 @@
       expect(http.read("http://127.0.0.1:${server.port}/"),
           completion(equals("got request")));
 
-      return supportsIpV6.then((supportsIpV6) {
+      return _supportsIpV6.then((supportsIpV6) {
         if (!supportsIpV6) return;
         expect(http.read("http://[::1]:${server.port}/"),
             completion(equals("got request")));
@@ -117,6 +117,26 @@
   });
 }
 
+/// A cache for [supportsIpV6].
+bool _supportsIpV6Cache;
+
+// TODO(nweiz): This is known to be inaccurate on Windows machines with IPv6
+// disabled (issue 19815). Tests will fail on such machines.
+/// Returns whether this computer supports binding to IPv6 addresses.
+Future<bool> get _supportsIpV6 {
+  if (_supportsIpV6Cache != null) return new Future.value(_supportsIpV6Cache);
+
+  return ServerSocket.bind(InternetAddress.LOOPBACK_IP_V6, 0).then((socket) {
+    _supportsIpV6Cache = true;
+    socket.close();
+    return true;
+  }).catchError((error) {
+    if (error is! SocketException) throw error;
+    _supportsIpV6Cache = false;
+    return false;
+  });
+}
+
 /// Makes a GET request to the root of [server] and returns the response.
 Future<http.Response> _get(HttpServer server) => http.get(_urlFor(server));