Unverified Commit 0fa5ba43 authored by Ian Hickson's avatar Ian Hickson Committed by GitHub

Revert "Remove race conditions involving finding available ports (#18488)" (#18521)

This reverts commit 77508722.
parent 77508722
...@@ -35,14 +35,12 @@ void main() { ...@@ -35,14 +35,12 @@ void main() {
.listen((String line) { .listen((String line) {
print('run:stdout: $line'); print('run:stdout: $line');
stdout.add(line); stdout.add(line);
if (vmServicePort == null) { if (lineContainsServicePort(line)) {
vmServicePort = parseServicePort(line); vmServicePort = parseServicePort(line);
if (vmServicePort != null) { print('service protocol connection available at port $vmServicePort');
print('service protocol connection available at port $vmServicePort'); print('run: ready!');
print('run: ready!'); ready.complete();
ready.complete(); ok ??= true;
ok ??= true;
}
} }
}); });
run.stderr run.stderr
......
...@@ -41,14 +41,12 @@ void main() { ...@@ -41,14 +41,12 @@ void main() {
.transform(const LineSplitter()) .transform(const LineSplitter())
.listen((String line) { .listen((String line) {
print('run:stdout: $line'); print('run:stdout: $line');
if (vmServicePort == null) { if (lineContainsServicePort(line)) {
vmServicePort = parseServicePort(line); vmServicePort = parseServicePort(line);
if (vmServicePort != null) { print('service protocol connection available at port $vmServicePort');
print('service protocol connection available at port $vmServicePort'); print('run: ready!');
print('run: ready!'); ready.complete();
ready.complete(); ok ??= true;
ok ??= true;
}
} }
}); });
run.stderr run.stderr
......
...@@ -33,14 +33,12 @@ void main() { ...@@ -33,14 +33,12 @@ void main() {
.transform(const LineSplitter()) .transform(const LineSplitter())
.listen((String line) { .listen((String line) {
print('run:stdout: $line'); print('run:stdout: $line');
if (vmServicePort == null) { if (lineContainsServicePort(line)) {
vmServicePort = parseServicePort(line); vmServicePort = parseServicePort(line);
if (vmServicePort != null) { print('service protocol connection available at port $vmServicePort');
print('service protocol connection available at port $vmServicePort'); print('run: ready!');
print('run: ready!'); ready.complete();
ready.complete(); ok ??= true;
ok ??= true;
}
} }
}); });
run.stderr run.stderr
......
...@@ -28,8 +28,9 @@ Future<Map<String, dynamic>> runTask(String taskName, { bool silent = false }) a ...@@ -28,8 +28,9 @@ Future<Map<String, dynamic>> runTask(String taskName, { bool silent = false }) a
if (!file(taskExecutable).existsSync()) if (!file(taskExecutable).existsSync())
throw 'Executable Dart file not found: $taskExecutable'; throw 'Executable Dart file not found: $taskExecutable';
final int vmServicePort = await findAvailablePort();
final Process runner = await startProcess(dartBin, <String>[ final Process runner = await startProcess(dartBin, <String>[
'--enable-vm-service=0', '--enable-vm-service=$vmServicePort',
'--no-pause-isolates-on-exit', '--no-pause-isolates-on-exit',
taskExecutable, taskExecutable,
]); ]);
...@@ -40,17 +41,10 @@ Future<Map<String, dynamic>> runTask(String taskName, { bool silent = false }) a ...@@ -40,17 +41,10 @@ Future<Map<String, dynamic>> runTask(String taskName, { bool silent = false }) a
runnerFinished = true; runnerFinished = true;
}); });
final Completer<int> port = new Completer<int>();
final StreamSubscription<String> stdoutSub = runner.stdout final StreamSubscription<String> stdoutSub = runner.stdout
.transform(const Utf8Decoder()) .transform(const Utf8Decoder())
.transform(const LineSplitter()) .transform(const LineSplitter())
.listen((String line) { .listen((String line) {
if (!port.isCompleted) {
final int portValue = parseServicePort(line, prefix: 'Observatory listening on ');
if (portValue != null)
port.complete(portValue);
}
if (!silent) { if (!silent) {
stdout.writeln('[$taskName] [STDOUT] $line'); stdout.writeln('[$taskName] [STDOUT] $line');
} }
...@@ -65,7 +59,7 @@ Future<Map<String, dynamic>> runTask(String taskName, { bool silent = false }) a ...@@ -65,7 +59,7 @@ Future<Map<String, dynamic>> runTask(String taskName, { bool silent = false }) a
String waitingFor = 'connection'; String waitingFor = 'connection';
try { try {
final VMIsolateRef isolate = await _connectToRunnerIsolate(await port.future); final VMIsolateRef isolate = await _connectToRunnerIsolate(vmServicePort);
waitingFor = 'task completion'; waitingFor = 'task completion';
final Map<String, dynamic> taskResult = final Map<String, dynamic> taskResult =
await isolate.invokeExtension('ext.cocoonRunTask').timeout(taskTimeoutWithGracePeriod); await isolate.invokeExtension('ext.cocoonRunTask').timeout(taskTimeoutWithGracePeriod);
......
...@@ -480,6 +480,21 @@ Future<Null> runAndCaptureAsyncStacks(Future<Null> callback()) { ...@@ -480,6 +480,21 @@ Future<Null> runAndCaptureAsyncStacks(Future<Null> callback()) {
return completer.future; return completer.future;
} }
/// Return an unused TCP port number.
Future<int> findAvailablePort() async {
int port = 20000;
while (true) {
try {
final ServerSocket socket =
await ServerSocket.bind(InternetAddress.LOOPBACK_IP_V4, port); // ignore: deprecated_member_use
await socket.close();
return port;
} catch (_) {
port++;
}
}
}
bool canRun(String path) => _processManager.canRun(path); bool canRun(String path) => _processManager.canRun(path);
String extractCloudAuthTokenArg(List<String> rawArgs) { String extractCloudAuthTokenArg(List<String> rawArgs) {
...@@ -502,20 +517,13 @@ String extractCloudAuthTokenArg(List<String> rawArgs) { ...@@ -502,20 +517,13 @@ String extractCloudAuthTokenArg(List<String> rawArgs) {
return token; return token;
} }
/// Tries to extract a port from the string. // "An Observatory debugger and profiler on ... is available at: http://127.0.0.1:8100/"
/// final RegExp _kObservatoryRegExp = new RegExp(r'An Observatory debugger .* is available at: (\S+:(\d+))');
/// The `prefix`, if specified, is a regular expression pattern and must not contain groups.
/// bool lineContainsServicePort(String line) => line.contains(_kObservatoryRegExp);
/// The `multiLine` flag should be set to true if `line` is actually a buffer of many lines.
int parseServicePort(String line, { int parseServicePort(String line) {
String prefix = 'An Observatory debugger .* is available at: ', final Match match = _kObservatoryRegExp.firstMatch(line);
bool multiLine = false,
}) {
// e.g. "An Observatory debugger and profiler on ... is available at: http://127.0.0.1:8100/"
final RegExp pattern = new RegExp('$prefix(\\S+:(\\d+)/\\S*)\$', multiLine: multiLine);
final Match match = pattern.firstMatch(line);
print(pattern);
print(match);
return match == null ? null : int.parse(match.group(2)); return match == null ? null : int.parse(match.group(2));
} }
......
...@@ -364,6 +364,8 @@ class MemoryTest { ...@@ -364,6 +364,8 @@ class MemoryTest {
if (deviceOperatingSystem == DeviceOperatingSystem.ios) if (deviceOperatingSystem == DeviceOperatingSystem.ios)
await prepareProvisioningCertificates(testDirectory); await prepareProvisioningCertificates(testDirectory);
final int observatoryPort = await findAvailablePort();
final List<String> runOptions = <String>[ final List<String> runOptions = <String>[
'-v', '-v',
'--profile', '--profile',
...@@ -371,14 +373,11 @@ class MemoryTest { ...@@ -371,14 +373,11 @@ class MemoryTest {
'-d', '-d',
deviceId, deviceId,
'--observatory-port', '--observatory-port',
'0', observatoryPort.toString(),
]; ];
if (testTarget != null) if (testTarget != null)
runOptions.addAll(<String>['-t', testTarget]); runOptions.addAll(<String>['-t', testTarget]);
final String output = await evalFlutter('run', options: runOptions); await flutter('run', options: runOptions);
final int observatoryPort = parseServicePort(output, prefix: 'Successfully connected to service protocol: ', multiLine: true);
if (observatoryPort == null)
throw new Exception('Could not find observatory port in "flutter run" output.');
final Map<String, dynamic> startData = await device.getMemoryStats(packageName); final Map<String, dynamic> startData = await device.getMemoryStats(packageName);
......
...@@ -15,6 +15,7 @@ import '../base/common.dart' show throwToolExit; ...@@ -15,6 +15,7 @@ import '../base/common.dart' show throwToolExit;
import '../base/file_system.dart'; import '../base/file_system.dart';
import '../base/io.dart'; import '../base/io.dart';
import '../base/logger.dart'; import '../base/logger.dart';
import '../base/port_scanner.dart';
import '../base/process.dart'; import '../base/process.dart';
import '../base/process_manager.dart'; import '../base/process_manager.dart';
import '../base/utils.dart'; import '../base/utils.dart';
...@@ -843,7 +844,7 @@ class _AndroidDevicePortForwarder extends DevicePortForwarder { ...@@ -843,7 +844,7 @@ class _AndroidDevicePortForwarder extends DevicePortForwarder {
final int devicePort = _extractPort(splitLine[2]); final int devicePort = _extractPort(splitLine[2]);
// Failed, skip. // Failed, skip.
if (hostPort == null || devicePort == null) if ((hostPort == null) || (devicePort == null))
continue; continue;
ports.add(new ForwardedPort(hostPort, devicePort)); ports.add(new ForwardedPort(hostPort, devicePort));
...@@ -854,29 +855,15 @@ class _AndroidDevicePortForwarder extends DevicePortForwarder { ...@@ -854,29 +855,15 @@ class _AndroidDevicePortForwarder extends DevicePortForwarder {
} }
@override @override
Future<int> forward(int devicePort, {int hostPort}) async { Future<int> forward(int devicePort, { int hostPort }) async {
hostPort ??= 0; if ((hostPort == null) || (hostPort == 0)) {
final RunResult process = await runCheckedAsync(device.adbCommandForDevice( // Auto select host port.
<String>['forward', 'tcp:$hostPort', 'tcp:$devicePort'] hostPort = await portScanner.findAvailablePort();
));
if (process.stderr.isNotEmpty)
process.throwException('adb returned error:\n${process.stderr}');
if (process.exitCode != 0) {
if (process.stdout.isNotEmpty)
process.throwException('adb returned error:\n${process.stdout}');
process.throwException('adb failed without a message');
} }
if (hostPort == 0) { await runCheckedAsync(device.adbCommandForDevice(
if (process.stdout.isEmpty) <String>['forward', 'tcp:$hostPort', 'tcp:$devicePort']
process.throwException('adb did not report forwarded port'); ));
hostPort = int.tryParse(process.stdout) ?? (throw 'adb returned invalid port number:\n${process.stdout}');
} else {
if (process.stdout.isNotEmpty)
process.throwException('adb returned error:\n${process.stdout}');
}
return hostPort; return hostPort;
} }
......
...@@ -5,6 +5,8 @@ ...@@ -5,6 +5,8 @@
import 'file_system.dart'; import 'file_system.dart';
import 'platform.dart'; import 'platform.dart';
const int kDefaultObservatoryPort = 8100;
/// Return the absolute path of the user's home directory /// Return the absolute path of the user's home directory
String get homeDirPath { String get homeDirPath {
if (_homeDirPath == null) { if (_homeDirPath == null) {
......
// Copyright 2017 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:async';
import 'context.dart';
import 'io.dart';
const int _kMaxSearchIterations = 20;
PortScanner get portScanner => context[PortScanner];
abstract class PortScanner {
const PortScanner();
/// Returns true if the specified [port] is available to bind to.
Future<bool> isPortAvailable(int port);
/// Returns an available ephemeral port.
Future<int> findAvailablePort();
/// Returns an available port as close to [defaultPort] as possible.
///
/// If [defaultPort] is available, this will return it. Otherwise, it will
/// search for an available port close to [defaultPort]. If it cannot find one,
/// it will return any available port.
Future<int> findPreferredPort(int defaultPort) async {
int iterationCount = 0;
while (iterationCount < _kMaxSearchIterations) {
final int port = defaultPort + iterationCount;
if (await isPortAvailable(port))
return port;
iterationCount++;
}
return findAvailablePort();
}
}
class HostPortScanner extends PortScanner {
const HostPortScanner();
@override
Future<bool> isPortAvailable(int port) async {
try {
// TODO(ianh): This is super racy.
final ServerSocket socket = await ServerSocket.bind(InternetAddress.LOOPBACK_IP_V4, port); // ignore: deprecated_member_use
await socket.close();
return true;
} catch (error) {
return false;
}
}
@override
Future<int> findAvailablePort() async {
ServerSocket socket;
try {
socket = await ServerSocket.bind(InternetAddress.LOOPBACK_IP_V4, 0); // ignore: deprecated_member_use
} on SocketException {
socket = await ServerSocket.bind(InternetAddress.LOOPBACK_IP_V6, 0, v6Only: true); // ignore: deprecated_member_use
}
final int port = socket.port;
await socket.close();
return port;
}
}
...@@ -229,7 +229,7 @@ Future<RunResult> runAsync(List<String> cmd, { ...@@ -229,7 +229,7 @@ Future<RunResult> runAsync(List<String> cmd, {
workingDirectory: workingDirectory, workingDirectory: workingDirectory,
environment: _environment(allowReentrantFlutter, environment), environment: _environment(allowReentrantFlutter, environment),
); );
final RunResult runResults = new RunResult(results, cmd); final RunResult runResults = new RunResult(results);
printTrace(runResults.toString()); printTrace(runResults.toString());
return runResults; return runResults;
} }
...@@ -240,10 +240,10 @@ Future<RunResult> runCheckedAsync(List<String> cmd, { ...@@ -240,10 +240,10 @@ Future<RunResult> runCheckedAsync(List<String> cmd, {
Map<String, String> environment Map<String, String> environment
}) async { }) async {
final RunResult result = await runAsync( final RunResult result = await runAsync(
cmd, cmd,
workingDirectory: workingDirectory, workingDirectory: workingDirectory,
allowReentrantFlutter: allowReentrantFlutter, allowReentrantFlutter: allowReentrantFlutter,
environment: environment, environment: environment
); );
if (result.exitCode != 0) if (result.exitCode != 0)
throw 'Exit code ${result.exitCode} from: ${cmd.join(' ')}:\n$result'; throw 'Exit code ${result.exitCode} from: ${cmd.join(' ')}:\n$result';
...@@ -364,12 +364,10 @@ class ProcessExit implements Exception { ...@@ -364,12 +364,10 @@ class ProcessExit implements Exception {
} }
class RunResult { class RunResult {
RunResult(this.processResult, this._command) : assert(_command != null), assert(_command.isNotEmpty); RunResult(this.processResult);
final ProcessResult processResult; final ProcessResult processResult;
final List<String> _command;
int get exitCode => processResult.exitCode; int get exitCode => processResult.exitCode;
String get stdout => processResult.stdout; String get stdout => processResult.stdout;
String get stderr => processResult.stderr; String get stderr => processResult.stderr;
...@@ -383,14 +381,4 @@ class RunResult { ...@@ -383,14 +381,4 @@ class RunResult {
out.writeln(processResult.stderr); out.writeln(processResult.stderr);
return out.toString().trimRight(); return out.toString().trimRight();
} }
/// Throws a [ProcessException] with the given `message`.
void throwException(String message) {
throw new ProcessException(
_command.first,
_command.skip(1).toList(),
message,
exitCode,
);
}
} }
...@@ -55,7 +55,8 @@ abstract class RunCommandBase extends FlutterCommand { ...@@ -55,7 +55,8 @@ abstract class RunCommandBase extends FlutterCommand {
void usesPortOptions() { void usesPortOptions() {
argParser.addOption('observatory-port', argParser.addOption('observatory-port',
help: 'Listen to the given port for an observatory debugger connection.\n' help: 'Listen to the given port for an observatory debugger connection.\n'
'Specifying port 0 (the default) will find a random free port.' 'Specifying port 0 will find a random free port.\n'
'Defaults to the first available port after $kDefaultObservatoryPort.'
); );
} }
......
...@@ -16,18 +16,14 @@ import '../tracing.dart'; ...@@ -16,18 +16,14 @@ import '../tracing.dart';
class TraceCommand extends FlutterCommand { class TraceCommand extends FlutterCommand {
TraceCommand() { TraceCommand() {
requiresPubspecYaml(); requiresPubspecYaml();
argParser.addOption('debug-port', argParser.addFlag('start', negatable: false, help: 'Start tracing.');
help: 'Local port where the observatory is listening. Required.', argParser.addFlag('stop', negatable: false, help: 'Stop tracing.');
);
argParser.addFlag('start', negatable: false, help: 'Start tracing. Implied if --stop is also omitted.');
argParser.addFlag('stop', negatable: false, help: 'Stop tracing. Implied if --start is also omitted.');
argParser.addOption('duration',
abbr: 'd',
help: 'Time to wait after starting (if --start is specified or implied) and before\n'
'stopping (if --stop is specified or implied).\n'
'Defaults to ten seconds if --stop is specified or implied, zero otherwise.',
);
argParser.addOption('out', help: 'Specify the path of the saved trace file.'); argParser.addOption('out', help: 'Specify the path of the saved trace file.');
argParser.addOption('duration',
defaultsTo: '10', abbr: 'd', help: 'Duration in seconds to trace.');
argParser.addOption('debug-port',
defaultsTo: kDefaultObservatoryPort.toString(),
help: 'Local port where the observatory is listening.');
} }
@override @override
...@@ -38,39 +34,13 @@ class TraceCommand extends FlutterCommand { ...@@ -38,39 +34,13 @@ class TraceCommand extends FlutterCommand {
@override @override
final String usageFooter = final String usageFooter =
'\`trace\` called without the --start or --stop flags will automatically start tracing,\n' '\`trace\` called with no arguments will automatically start tracing, delay a set amount of\n'
'delay a set amount of time (controlled by --duration), and stop tracing. To explicitly\n' 'time (controlled by --duration), and stop tracing. To explicitly control tracing, call trace\n'
'control tracing, call trace with --start and later with --stop.\n' 'with --start and later with --stop.';
'The --debug-port argument is required.';
@override @override
Future<Null> runCommand() async { Future<Null> runCommand() async {
int observatoryPort; final int observatoryPort = int.parse(argResults['debug-port']);
if (argResults.wasParsed('debug-port')) {
observatoryPort = int.tryParse(argResults['debug-port']);
}
if (observatoryPort == null) {
throwToolExit('The --debug-port argument must be specified.');
}
bool start = argResults['start'];
bool stop = argResults['stop'];
if (!start && !stop) {
start = true;
stop = true;
}
assert(start || stop);
Duration duration;
if (argResults.wasParsed('duration')) {
try {
duration = new Duration(seconds: int.parse(argResults['duration']));
} on FormatException {
throwToolExit('Invalid duration passed to --duration; it should be a positive number of seconds.');
}
} else {
duration = stop ? const Duration(seconds: 10) : Duration.zero;
}
// TODO(danrubel): this will break if we move to the new observatory URL // TODO(danrubel): this will break if we move to the new observatory URL
// See https://github.com/flutter/flutter/issues/7038 // See https://github.com/flutter/flutter/issues/7038
...@@ -86,11 +56,20 @@ class TraceCommand extends FlutterCommand { ...@@ -86,11 +56,20 @@ class TraceCommand extends FlutterCommand {
Cache.releaseLockEarly(); Cache.releaseLockEarly();
if (start) if ((!argResults['start'] && !argResults['stop']) ||
(argResults['start'] && argResults['stop'])) {
// Setting neither flags or both flags means do both commands and wait
// duration seconds in between.
await tracing.startTracing(); await tracing.startTracing();
await new Future<Null>.delayed(duration); await new Future<Null>.delayed(
if (stop) new Duration(seconds: int.parse(argResults['duration'])),
() => _stopTracing(tracing)
);
} else if (argResults['stop']) {
await _stopTracing(tracing); await _stopTracing(tracing);
} else {
await tracing.startTracing();
}
} }
Future<Null> _stopTracing(Tracing tracing) async { Future<Null> _stopTracing(Tracing tracing) async {
......
...@@ -19,6 +19,7 @@ import 'base/io.dart'; ...@@ -19,6 +19,7 @@ import 'base/io.dart';
import 'base/logger.dart'; import 'base/logger.dart';
import 'base/os.dart'; import 'base/os.dart';
import 'base/platform.dart'; import 'base/platform.dart';
import 'base/port_scanner.dart';
import 'base/utils.dart'; import 'base/utils.dart';
import 'cache.dart'; import 'cache.dart';
import 'compile.dart'; import 'compile.dart';
...@@ -69,6 +70,7 @@ Future<T> runInContext<T>( ...@@ -69,6 +70,7 @@ Future<T> runInContext<T>(
KernelCompiler: () => const KernelCompiler(), KernelCompiler: () => const KernelCompiler(),
Logger: () => platform.isWindows ? new WindowsStdoutLogger() : new StdoutLogger(), Logger: () => platform.isWindows ? new WindowsStdoutLogger() : new StdoutLogger(),
OperatingSystemUtils: () => new OperatingSystemUtils(), OperatingSystemUtils: () => new OperatingSystemUtils(),
PortScanner: () => const HostPortScanner(),
SimControl: () => new SimControl(), SimControl: () => new SimControl(),
Stdio: () => const Stdio(), Stdio: () => const Stdio(),
Usage: () => new Usage(), Usage: () => new Usage(),
......
...@@ -7,8 +7,10 @@ import 'dart:math' as math; ...@@ -7,8 +7,10 @@ import 'dart:math' as math;
import 'android/android_device.dart'; import 'android/android_device.dart';
import 'application_package.dart'; import 'application_package.dart';
import 'base/common.dart';
import 'base/context.dart'; import 'base/context.dart';
import 'base/file_system.dart'; import 'base/file_system.dart';
import 'base/port_scanner.dart';
import 'base/utils.dart'; import 'base/utils.dart';
import 'build_info.dart'; import 'build_info.dart';
import 'globals.dart'; import 'globals.dart';
...@@ -365,6 +367,14 @@ class DebuggingOptions { ...@@ -365,6 +367,14 @@ class DebuggingOptions {
final int observatoryPort; final int observatoryPort;
bool get hasObservatoryPort => observatoryPort != null; bool get hasObservatoryPort => observatoryPort != null;
/// Return the user specified observatory port. If that isn't available,
/// return [kDefaultObservatoryPort], or a port close to that one.
Future<int> findBestObservatoryPort() {
if (hasObservatoryPort)
return new Future<int>.value(observatoryPort);
return portScanner.findPreferredPort(observatoryPort ?? kDefaultObservatoryPort);
}
} }
class LaunchResult { class LaunchResult {
...@@ -404,9 +414,9 @@ abstract class DevicePortForwarder { ...@@ -404,9 +414,9 @@ abstract class DevicePortForwarder {
List<ForwardedPort> get forwardedPorts; List<ForwardedPort> get forwardedPorts;
/// Forward [hostPort] on the host to [devicePort] on the device. /// Forward [hostPort] on the host to [devicePort] on the device.
/// If [hostPort] is null or zero, will auto select a host port. /// If [hostPort] is null, will auto select a host port.
/// Returns a Future that completes with the host port. /// Returns a Future that completes with the host port.
Future<int> forward(int devicePort, {int hostPort}); Future<int> forward(int devicePort, { int hostPort });
/// Stops forwarding [forwardedPort]. /// Stops forwarding [forwardedPort].
Future<Null> unforward(ForwardedPort forwardedPort); Future<Null> unforward(ForwardedPort forwardedPort);
......
...@@ -10,6 +10,7 @@ import '../base/file_system.dart'; ...@@ -10,6 +10,7 @@ import '../base/file_system.dart';
import '../base/io.dart'; import '../base/io.dart';
import '../base/logger.dart'; import '../base/logger.dart';
import '../base/platform.dart'; import '../base/platform.dart';
import '../base/port_scanner.dart';
import '../base/process.dart'; import '../base/process.dart';
import '../base/process_manager.dart'; import '../base/process_manager.dart';
import '../build_info.dart'; import '../build_info.dart';
...@@ -508,40 +509,26 @@ class _IOSDevicePortForwarder extends DevicePortForwarder { ...@@ -508,40 +509,26 @@ class _IOSDevicePortForwarder extends DevicePortForwarder {
@override @override
Future<int> forward(int devicePort, {int hostPort}) async { Future<int> forward(int devicePort, {int hostPort}) async {
final bool autoselect = hostPort == null || hostPort == 0; if ((hostPort == null) || (hostPort == 0)) {
if (autoselect) // Auto select host port.
hostPort = 1024; hostPort = await portScanner.findAvailablePort();
Process process;
bool connected = false;
while (!connected) {
// Usage: iproxy LOCAL_TCP_PORT DEVICE_TCP_PORT UDID
process = await runCommand(<String>[
device._iproxyPath,
hostPort.toString(),
devicePort.toString(),
device.id,
]);
connected = !await process.stdout.isEmpty;
if (!connected) {
if (autoselect) {
hostPort += 1;
if (hostPort > 65535)
throw 'Could not find open port on host.';
} else {
throw 'Port $hostPort is not available.';
}
}
} }
assert(connected);
assert(process != null);
final ForwardedPort forwardedPort = new ForwardedPort.withContext( // Usage: iproxy LOCAL_TCP_PORT DEVICE_TCP_PORT UDID
hostPort, devicePort, process, final Process process = await runCommand(<String>[
); device._iproxyPath,
hostPort.toString(),
devicePort.toString(),
device.id,
]);
final ForwardedPort forwardedPort = new ForwardedPort.withContext(hostPort,
devicePort, process);
printTrace('Forwarded port $forwardedPort'); printTrace('Forwarded port $forwardedPort');
_forwardedPorts.add(forwardedPort); _forwardedPorts.add(forwardedPort);
return hostPort; return hostPort;
} }
......
...@@ -305,7 +305,8 @@ class IOSSimulator extends Device { ...@@ -305,7 +305,8 @@ class IOSSimulator extends Device {
args.add('--skia-deterministic-rendering'); args.add('--skia-deterministic-rendering');
if (debuggingOptions.useTestFonts) if (debuggingOptions.useTestFonts)
args.add('--use-test-fonts'); args.add('--use-test-fonts');
final int observatoryPort = debuggingOptions.observatoryPort ?? 0;
final int observatoryPort = await debuggingOptions.findBestObservatoryPort();
args.add('--observatory-port=$observatoryPort'); args.add('--observatory-port=$observatoryPort');
} }
...@@ -692,7 +693,7 @@ class _IOSSimulatorDevicePortForwarder extends DevicePortForwarder { ...@@ -692,7 +693,7 @@ class _IOSSimulatorDevicePortForwarder extends DevicePortForwarder {
@override @override
Future<int> forward(int devicePort, {int hostPort}) async { Future<int> forward(int devicePort, {int hostPort}) async {
if (hostPort == null || hostPort == 0) { if ((hostPort == null) || (hostPort == 0)) {
hostPort = devicePort; hostPort = devicePort;
} }
assert(devicePort == hostPort); assert(devicePort == hostPort);
......
...@@ -4,7 +4,9 @@ ...@@ -4,7 +4,9 @@
import 'dart:async'; import 'dart:async';
import 'base/common.dart';
import 'base/io.dart'; import 'base/io.dart';
import 'base/port_scanner.dart';
import 'device.dart'; import 'device.dart';
import 'globals.dart'; import 'globals.dart';
...@@ -16,8 +18,10 @@ class ProtocolDiscovery { ...@@ -16,8 +18,10 @@ class ProtocolDiscovery {
this.serviceName, { this.serviceName, {
this.portForwarder, this.portForwarder,
this.hostPort, this.hostPort,
this.defaultHostPort,
this.ipv6, this.ipv6,
}) : assert(logReader != null), }) : assert(logReader != null),
assert(portForwarder == null || defaultHostPort != null),
_prefix = '$serviceName listening on ' { _prefix = '$serviceName listening on ' {
_deviceLogSubscription = logReader.logLines.listen(_handleLine); _deviceLogSubscription = logReader.logLines.listen(_handleLine);
} }
...@@ -33,6 +37,7 @@ class ProtocolDiscovery { ...@@ -33,6 +37,7 @@ class ProtocolDiscovery {
logReader, kObservatoryService, logReader, kObservatoryService,
portForwarder: portForwarder, portForwarder: portForwarder,
hostPort: hostPort, hostPort: hostPort,
defaultHostPort: kDefaultObservatoryPort,
ipv6: ipv6, ipv6: ipv6,
); );
} }
...@@ -41,6 +46,7 @@ class ProtocolDiscovery { ...@@ -41,6 +46,7 @@ class ProtocolDiscovery {
final String serviceName; final String serviceName;
final DevicePortForwarder portForwarder; final DevicePortForwarder portForwarder;
final int hostPort; final int hostPort;
final int defaultHostPort;
final bool ipv6; final bool ipv6;
final String _prefix; final String _prefix;
...@@ -82,15 +88,16 @@ class ProtocolDiscovery { ...@@ -82,15 +88,16 @@ class ProtocolDiscovery {
Uri hostUri = deviceUri; Uri hostUri = deviceUri;
if (portForwarder != null) { if (portForwarder != null) {
final int actualDevicePort = deviceUri.port; final int devicePort = deviceUri.port;
final int actualHostPort = await portForwarder.forward(actualDevicePort, hostPort: hostPort); int hostPort = this.hostPort ?? await portScanner.findPreferredPort(defaultHostPort);
printTrace('Forwarded host port $actualHostPort to device port $actualDevicePort for $serviceName'); hostPort = await portForwarder.forward(devicePort, hostPort: hostPort);
hostUri = deviceUri.replace(port: actualHostPort); printTrace('Forwarded host port $hostPort to device port $devicePort for $serviceName');
hostUri = deviceUri.replace(port: hostPort);
} }
assert(new InternetAddress(hostUri.host).isLoopback); assert(new InternetAddress(hostUri.host).isLoopback);
if (ipv6) { if (ipv6) {
hostUri = hostUri.replace(host: InternetAddress.loopbackIPv6.host); hostUri = hostUri.replace(host: InternetAddress.LOOPBACK_IP_V6.host); // ignore: deprecated_member_use
} }
return hostUri; return hostUri;
......
...@@ -122,7 +122,8 @@ class FlutterTesterDevice extends Device { ...@@ -122,7 +122,8 @@ class FlutterTesterDevice extends Device {
command.add('--start-paused'); command.add('--start-paused');
} }
if (debuggingOptions.hasObservatoryPort) if (debuggingOptions.hasObservatoryPort)
command.add('--observatory-port=${debuggingOptions.observatoryPort}'); command
.add('--observatory-port=${debuggingOptions.hasObservatoryPort}');
} }
// Build assets and perform initial compilation. // Build assets and perform initial compilation.
...@@ -169,10 +170,9 @@ class FlutterTesterDevice extends Device { ...@@ -169,10 +170,9 @@ class FlutterTesterDevice extends Device {
if (!debuggingOptions.debuggingEnabled) if (!debuggingOptions.debuggingEnabled)
return new LaunchResult.succeeded(); return new LaunchResult.succeeded();
final ProtocolDiscovery observatoryDiscovery = new ProtocolDiscovery.observatory( final ProtocolDiscovery observatoryDiscovery =
getLogReader(), new ProtocolDiscovery.observatory(getLogReader(),
hostPort: debuggingOptions.observatoryPort, hostPort: debuggingOptions.observatoryPort);
);
final Uri observatoryUri = await observatoryDiscovery.uri; final Uri observatoryUri = await observatoryDiscovery.uri;
return new LaunchResult.succeeded(observatoryUri: observatoryUri); return new LaunchResult.succeeded(observatoryUri: observatoryUri);
......
...@@ -381,7 +381,7 @@ void main() { ...@@ -381,7 +381,7 @@ void main() {
fs.path.join(outputPath, 'snapshot.d'): '${fs.path.join(outputPath, 'snapshot_assembly.S')} : ', fs.path.join(outputPath, 'snapshot.d'): '${fs.path.join(outputPath, 'snapshot_assembly.S')} : ',
}; };
final RunResult successResult = new RunResult(new ProcessResult(1, 0, '', ''), <String>['command name', 'arguments...']); final RunResult successResult = new RunResult(new ProcessResult(1, 0, '', ''));
when(xcode.cc(any)).thenAnswer((_) => new Future<RunResult>.value(successResult)); when(xcode.cc(any)).thenAnswer((_) => new Future<RunResult>.value(successResult));
when(xcode.clang(any)).thenAnswer((_) => new Future<RunResult>.value(successResult)); when(xcode.clang(any)).thenAnswer((_) => new Future<RunResult>.value(successResult));
...@@ -428,7 +428,7 @@ void main() { ...@@ -428,7 +428,7 @@ void main() {
fs.path.join(outputPath, 'snapshot.d'): '${fs.path.join(outputPath, 'snapshot_assembly.S')} : ', fs.path.join(outputPath, 'snapshot.d'): '${fs.path.join(outputPath, 'snapshot_assembly.S')} : ',
}; };
final RunResult successResult = new RunResult(new ProcessResult(1, 0, '', ''), <String>['command name', 'arguments...']); final RunResult successResult = new RunResult(new ProcessResult(1, 0, '', ''));
when(xcode.cc(any)).thenAnswer((_) => new Future<RunResult>.value(successResult)); when(xcode.cc(any)).thenAnswer((_) => new Future<RunResult>.value(successResult));
when(xcode.clang(any)).thenAnswer((_) => new Future<RunResult>.value(successResult)); when(xcode.clang(any)).thenAnswer((_) => new Future<RunResult>.value(successResult));
...@@ -476,7 +476,7 @@ void main() { ...@@ -476,7 +476,7 @@ void main() {
fs.path.join(outputPath, 'snapshot.d'): '${fs.path.join(outputPath, 'vm_snapshot_data')} : ', fs.path.join(outputPath, 'snapshot.d'): '${fs.path.join(outputPath, 'vm_snapshot_data')} : ',
}; };
final RunResult successResult = new RunResult(new ProcessResult(1, 0, '', ''), <String>['command name', 'arguments...']); final RunResult successResult = new RunResult(new ProcessResult(1, 0, '', ''));
when(xcode.cc(any)).thenAnswer((_) => new Future<RunResult>.value(successResult)); when(xcode.cc(any)).thenAnswer((_) => new Future<RunResult>.value(successResult));
when(xcode.clang(any)).thenAnswer((_) => new Future<RunResult>.value(successResult)); when(xcode.clang(any)).thenAnswer((_) => new Future<RunResult>.value(successResult));
...@@ -528,7 +528,7 @@ void main() { ...@@ -528,7 +528,7 @@ void main() {
fs.path.join(outputPath, 'snapshot.d'): '${fs.path.join(outputPath, 'vm_snapshot_data')} : ', fs.path.join(outputPath, 'snapshot.d'): '${fs.path.join(outputPath, 'vm_snapshot_data')} : ',
}; };
final RunResult successResult = new RunResult(new ProcessResult(1, 0, '', ''), <String>['command name', 'arguments...']); final RunResult successResult = new RunResult(new ProcessResult(1, 0, '', ''));
when(xcode.cc(any)).thenAnswer((_) => new Future<RunResult>.value(successResult)); when(xcode.cc(any)).thenAnswer((_) => new Future<RunResult>.value(successResult));
when(xcode.clang(any)).thenAnswer((_) => new Future<RunResult>.value(successResult)); when(xcode.clang(any)).thenAnswer((_) => new Future<RunResult>.value(successResult));
...@@ -575,7 +575,7 @@ void main() { ...@@ -575,7 +575,7 @@ void main() {
fs.path.join(outputPath, 'snapshot.d'): '${fs.path.join(outputPath, 'snapshot_assembly.S')} : ', fs.path.join(outputPath, 'snapshot.d'): '${fs.path.join(outputPath, 'snapshot_assembly.S')} : ',
}; };
final RunResult successResult = new RunResult(new ProcessResult(1, 0, '', ''), <String>['command name', 'arguments...']); final RunResult successResult = new RunResult(new ProcessResult(1, 0, '', ''));
when(xcode.cc(any)).thenAnswer((_) => new Future<RunResult>.value(successResult)); when(xcode.cc(any)).thenAnswer((_) => new Future<RunResult>.value(successResult));
when(xcode.clang(any)).thenAnswer((_) => new Future<RunResult>.value(successResult)); when(xcode.clang(any)).thenAnswer((_) => new Future<RunResult>.value(successResult));
...@@ -622,7 +622,7 @@ void main() { ...@@ -622,7 +622,7 @@ void main() {
fs.path.join(outputPath, 'snapshot.d'): '${fs.path.join(outputPath, 'snapshot_assembly.S')} : ', fs.path.join(outputPath, 'snapshot.d'): '${fs.path.join(outputPath, 'snapshot_assembly.S')} : ',
}; };
final RunResult successResult = new RunResult(new ProcessResult(1, 0, '', ''), <String>['command name', 'arguments...']); final RunResult successResult = new RunResult(new ProcessResult(1, 0, '', ''));
when(xcode.cc(any)).thenAnswer((_) => new Future<RunResult>.value(successResult)); when(xcode.cc(any)).thenAnswer((_) => new Future<RunResult>.value(successResult));
when(xcode.clang(any)).thenAnswer((_) => new Future<RunResult>.value(successResult)); when(xcode.clang(any)).thenAnswer((_) => new Future<RunResult>.value(successResult));
...@@ -689,7 +689,7 @@ void main() { ...@@ -689,7 +689,7 @@ void main() {
fs.path.join(outputPath, 'snapshot.d'): '${fs.path.join(outputPath, 'vm_snapshot_data')} : ', fs.path.join(outputPath, 'snapshot.d'): '${fs.path.join(outputPath, 'vm_snapshot_data')} : ',
}; };
final RunResult successResult = new RunResult(new ProcessResult(1, 0, '', ''), <String>['command name', 'arguments...']); final RunResult successResult = new RunResult(new ProcessResult(1, 0, '', ''));
when(xcode.cc(any)).thenAnswer((_) => new Future<RunResult>.value(successResult)); when(xcode.cc(any)).thenAnswer((_) => new Future<RunResult>.value(successResult));
when(xcode.clang(any)).thenAnswer((_) => new Future<RunResult>.value(successResult)); when(xcode.clang(any)).thenAnswer((_) => new Future<RunResult>.value(successResult));
...@@ -741,7 +741,7 @@ void main() { ...@@ -741,7 +741,7 @@ void main() {
fs.path.join(outputPath, 'snapshot.d'): '${fs.path.join(outputPath, 'vm_snapshot_data')} : ', fs.path.join(outputPath, 'snapshot.d'): '${fs.path.join(outputPath, 'vm_snapshot_data')} : ',
}; };
final RunResult successResult = new RunResult(new ProcessResult(1, 0, '', ''), <String>['command name', 'arguments...']); final RunResult successResult = new RunResult(new ProcessResult(1, 0, '', ''));
when(xcode.cc(any)).thenAnswer((_) => new Future<RunResult>.value(successResult)); when(xcode.cc(any)).thenAnswer((_) => new Future<RunResult>.value(successResult));
when(xcode.clang(any)).thenAnswer((_) => new Future<RunResult>.value(successResult)); when(xcode.clang(any)).thenAnswer((_) => new Future<RunResult>.value(successResult));
......
...@@ -115,16 +115,16 @@ void main() { ...@@ -115,16 +115,16 @@ void main() {
testUsingContext('default port', () async { testUsingContext('default port', () async {
final MockDeviceLogReader logReader = new MockDeviceLogReader(); final MockDeviceLogReader logReader = new MockDeviceLogReader();
final ProtocolDiscovery discoverer = new ProtocolDiscovery.observatory( final ProtocolDiscovery discoverer = new ProtocolDiscovery.observatory(
logReader, logReader,
portForwarder: new MockPortForwarder(99), portForwarder: new MockPortForwarder(99),
); hostPort: 54777);
// Get next port future. // Get next port future.
final Future<Uri> nextUri = discoverer.uri; final Future<Uri> nextUri = discoverer.uri;
logReader.addLine('I/flutter : Observatory listening on http://127.0.0.1:54804/PTwjm8Ii8qg=/'); logReader.addLine('I/flutter : Observatory listening on http://127.0.0.1:54804/PTwjm8Ii8qg=/');
final Uri uri = await nextUri; final Uri uri = await nextUri;
expect(uri.port, 99); expect(uri.port, 54777);
expect('$uri', 'http://127.0.0.1:99/PTwjm8Ii8qg=/'); expect('$uri', 'http://127.0.0.1:54777/PTwjm8Ii8qg=/');
discoverer.cancel(); discoverer.cancel();
logReader.dispose(); logReader.dispose();
...@@ -133,10 +133,9 @@ void main() { ...@@ -133,10 +133,9 @@ void main() {
testUsingContext('specified port', () async { testUsingContext('specified port', () async {
final MockDeviceLogReader logReader = new MockDeviceLogReader(); final MockDeviceLogReader logReader = new MockDeviceLogReader();
final ProtocolDiscovery discoverer = new ProtocolDiscovery.observatory( final ProtocolDiscovery discoverer = new ProtocolDiscovery.observatory(
logReader, logReader,
portForwarder: new MockPortForwarder(99), portForwarder: new MockPortForwarder(99),
hostPort: 1243, hostPort: 1243);
);
// Get next port future. // Get next port future.
final Future<Uri> nextUri = discoverer.uri; final Future<Uri> nextUri = discoverer.uri;
...@@ -149,33 +148,13 @@ void main() { ...@@ -149,33 +148,13 @@ void main() {
logReader.dispose(); logReader.dispose();
}); });
testUsingContext('specified port zero', () async {
final MockDeviceLogReader logReader = new MockDeviceLogReader();
final ProtocolDiscovery discoverer = new ProtocolDiscovery.observatory(
logReader,
portForwarder: new MockPortForwarder(99),
hostPort: 0,
);
// Get next port future.
final Future<Uri> nextUri = discoverer.uri;
logReader.addLine('I/flutter : Observatory listening on http://127.0.0.1:54804/PTwjm8Ii8qg=/');
final Uri uri = await nextUri;
expect(uri.port, 99);
expect('$uri', 'http://127.0.0.1:99/PTwjm8Ii8qg=/');
discoverer.cancel();
logReader.dispose();
});
testUsingContext('ipv6', () async { testUsingContext('ipv6', () async {
final MockDeviceLogReader logReader = new MockDeviceLogReader(); final MockDeviceLogReader logReader = new MockDeviceLogReader();
final ProtocolDiscovery discoverer = new ProtocolDiscovery.observatory( final ProtocolDiscovery discoverer = new ProtocolDiscovery.observatory(
logReader, logReader,
portForwarder: new MockPortForwarder(99), portForwarder: new MockPortForwarder(99),
hostPort: 54777, hostPort: 54777,
ipv6: true, ipv6: true);
);
// Get next port future. // Get next port future.
final Future<Uri> nextUri = discoverer.uri; final Future<Uri> nextUri = discoverer.uri;
...@@ -196,12 +175,7 @@ class MockPortForwarder extends DevicePortForwarder { ...@@ -196,12 +175,7 @@ class MockPortForwarder extends DevicePortForwarder {
MockPortForwarder([this.availablePort]); MockPortForwarder([this.availablePort]);
@override @override
Future<int> forward(int devicePort, {int hostPort}) async { Future<int> forward(int devicePort, {int hostPort}) async => hostPort ?? availablePort;
hostPort ??= 0;
if (hostPort == 0)
return availablePort;
return hostPort;
}
@override @override
List<ForwardedPort> get forwardedPorts => throw 'not implemented'; List<ForwardedPort> get forwardedPorts => throw 'not implemented';
......
...@@ -12,6 +12,7 @@ import 'package:flutter_tools/src/base/file_system.dart'; ...@@ -12,6 +12,7 @@ import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/io.dart'; import 'package:flutter_tools/src/base/io.dart';
import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/os.dart'; import 'package:flutter_tools/src/base/os.dart';
import 'package:flutter_tools/src/base/port_scanner.dart';
import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/context_runner.dart'; import 'package:flutter_tools/src/context_runner.dart';
import 'package:flutter_tools/src/device.dart'; import 'package:flutter_tools/src/device.dart';
...@@ -76,6 +77,7 @@ void testUsingContext(String description, dynamic testMethod(), { ...@@ -76,6 +77,7 @@ void testUsingContext(String description, dynamic testMethod(), {
}, },
Logger: () => new BufferLogger(), Logger: () => new BufferLogger(),
OperatingSystemUtils: () => new MockOperatingSystemUtils(), OperatingSystemUtils: () => new MockOperatingSystemUtils(),
PortScanner: () => new MockPortScanner(),
SimControl: () => new MockSimControl(), SimControl: () => new MockSimControl(),
Usage: () => new MockUsage(), Usage: () => new MockUsage(),
XcodeProjectInterpreter: () => new MockXcodeProjectInterpreter(), XcodeProjectInterpreter: () => new MockXcodeProjectInterpreter(),
...@@ -125,6 +127,16 @@ void _printBufferedErrors(AppContext testContext) { ...@@ -125,6 +127,16 @@ void _printBufferedErrors(AppContext testContext) {
} }
} }
class MockPortScanner extends PortScanner {
static int _nextAvailablePort = 12345;
@override
Future<bool> isPortAvailable(int port) async => true;
@override
Future<int> findAvailablePort() async => _nextAvailablePort++;
}
class MockDeviceManager implements DeviceManager { class MockDeviceManager implements DeviceManager {
List<Device> devices = <Device>[]; List<Device> devices = <Device>[];
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
import 'package:test/test.dart'; import 'package:test/test.dart';
import 'package:flutter_tools/src/base/port_scanner.dart';
import 'package:flutter_tools/src/vmservice.dart'; import 'package:flutter_tools/src/vmservice.dart';
import 'src/common.dart'; import 'src/common.dart';
...@@ -12,8 +13,9 @@ import 'src/context.dart'; ...@@ -12,8 +13,9 @@ import 'src/context.dart';
void main() { void main() {
group('VMService', () { group('VMService', () {
testUsingContext('fails connection eagerly in the connect() method', () async { testUsingContext('fails connection eagerly in the connect() method', () async {
final int port = await const HostPortScanner().findAvailablePort();
expect( expect(
VMService.connect(Uri.parse('http://host.invalid:9999/')), VMService.connect(Uri.parse('http://localhost:$port')),
throwsToolExit(), throwsToolExit(),
); );
}); });
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment