7a373c1984
BUG= R=vsm@google.com Review URL: https://codereview.chromium.org/1672563002 .
84 lines
2.5 KiB
Dart
84 lines
2.5 KiB
Dart
// Copyright (c) 2016, 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:io';
|
|
import 'dart:async';
|
|
|
|
class ChildServerProcess {
|
|
/// [IOSink]s like [stdout] don't like to be piped more than one [Stream], but
|
|
/// we want to pipe many of them (basically, every standard output and error
|
|
/// of every child process we open), so we pipe to an accommodating consumer.
|
|
static final _consoleOut = new _MultipleStreamConsumer(stdout);
|
|
|
|
final Process process;
|
|
final String host;
|
|
final int port;
|
|
ChildServerProcess._(this.process, this.host, this.port);
|
|
|
|
get httpUri => Uri.parse('http://$host:$port');
|
|
|
|
static build(Future<Process> builder(String host, int port),
|
|
{int defaultPort: 1024,
|
|
int maxPort: 65535,
|
|
String host: '0.0.0.0'}) async {
|
|
var port = await _findUnusedPort(defaultPort, maxPort);
|
|
var p = (await builder(host, port))
|
|
..stdout.pipe(_consoleOut)
|
|
..stderr.pipe(_consoleOut);
|
|
await _waitForServer(host, port);
|
|
return new ChildServerProcess._(p, host, port);
|
|
}
|
|
|
|
static _waitForServer(String host, int port,
|
|
{int attempts: 10,
|
|
Duration retryDelay: const Duration(seconds: 1)}) async {
|
|
var lastError;
|
|
for (int i = 0; i < attempts; i++) {
|
|
try {
|
|
await (await Socket.connect(host, port)).close();
|
|
return;
|
|
} catch (e) {
|
|
lastError = e;
|
|
await new Future.delayed(retryDelay);
|
|
}
|
|
}
|
|
throw new StateError(
|
|
'Failed to connect to $host:$port after $attempts attempts; '
|
|
'Last error:\n$lastError');
|
|
}
|
|
|
|
static Future<int> _findUnusedPort(int fromPort, int toPort) async {
|
|
var lastError;
|
|
for (int port = fromPort; port <= toPort; port++) {
|
|
try {
|
|
await (await ServerSocket.bind(InternetAddress.ANY_IP_V4, port))
|
|
.close();
|
|
return port;
|
|
} catch (e) {
|
|
lastError = e;
|
|
}
|
|
}
|
|
throw new StateError(
|
|
'Failed to find an unused port between $fromPort and $toPort; '
|
|
'Last error:\n$lastError');
|
|
}
|
|
}
|
|
|
|
/// A consumer into which we can pipe as many streams as we want, that forwards
|
|
/// everything to an [IOSink] (such as [stdout]).
|
|
class _MultipleStreamConsumer extends StreamConsumer<List<int>> {
|
|
final IOSink _sink;
|
|
_MultipleStreamConsumer(this._sink);
|
|
|
|
@override
|
|
Future addStream(Stream<List<int>> stream) async {
|
|
await for (var data in stream) {
|
|
_sink.add(data);
|
|
}
|
|
}
|
|
|
|
@override
|
|
close() {}
|
|
}
|