blob: 7ecca81ab7dbaca1b767f51ec9d0ffff5ef1a941 [file] [log] [blame]
// Copyright 2020 The Fuchsia 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:async';
import 'dart:io';
/// Repeatedly finds a probably-unused port on localhost and passes it to
/// [tryPort] until it binds successfully.
///
/// [tryPort] should return a non-`null` value or a Future completing to a
/// non-`null` value once it binds successfully. This value will be returned
/// by [getUnusedPort] in turn.
///
/// This is necessary for ensuring that our port binding isn't flaky for
/// applications that don't print out the bound port.
Future<T> getUnusedPort<T>(FutureOr<T> Function(int port) tryPort) async {
T value;
await Future.doWhile(() async {
value = await tryPort(await getUnsafeUnusedPort());
return value == null;
});
return value;
}
/// This bool is intended to guard from needing 2 binds for every port that we need.
/// Once an IPv6 bind fails, we assume it won't ever work and use IPv4 only
/// from there on out.
var _maySupportIPv6 = true;
/// Returns a port that is probably, but not definitely, not in use.
///
/// This has a built-in race condition: another process may bind this port at
/// any time after this call has returned. If at all possible, callers should
/// use [getUnusedPort] instead.
Future<int> getUnsafeUnusedPort() async {
int port;
if (_maySupportIPv6) {
try {
final socket = await RawServerSocket.bind(InternetAddress.loopbackIPv6, 0,
v6Only: true);
port = socket.port;
await socket.close();
} on SocketException {
_maySupportIPv6 = false;
}
}
if (!_maySupportIPv6) {
final socket = await RawServerSocket.bind(InternetAddress.loopbackIPv4, 0);
port = socket.port;
await socket.close();
}
return port;
}