Skip to content

Commit f3ce9d2

Browse files
authored
Make daemon server work on ipv6-only machines. (#144359)
Retry binding on ipv6 if binding on ipv4 failed.
1 parent ee6111a commit f3ce9d2

File tree

2 files changed

+115
-5
lines changed

2 files changed

+115
-5
lines changed

packages/flutter_tools/lib/src/commands/daemon.dart

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ class DaemonCommand extends FlutterCommand {
7373
throwToolExit('Invalid port for `--listen-on-tcp-port`: $error');
7474
}
7575

76-
await _DaemonServer(
76+
await DaemonServer(
7777
port: port,
7878
logger: StdoutLogger(
7979
terminal: globals.terminal,
@@ -100,12 +100,14 @@ class DaemonCommand extends FlutterCommand {
100100
}
101101
}
102102

103-
class _DaemonServer {
104-
_DaemonServer({
103+
@visibleForTesting
104+
class DaemonServer {
105+
DaemonServer({
105106
this.port,
106107
required this.logger,
107108
this.notifyingLogger,
108-
});
109+
@visibleForTesting Future<ServerSocket> Function(InternetAddress address, int port) bind = ServerSocket.bind,
110+
}) : _bind = bind;
109111

110112
final int? port;
111113

@@ -115,8 +117,20 @@ class _DaemonServer {
115117
// Logger that sends the message to the other end of daemon connection.
116118
final NotifyingLogger? notifyingLogger;
117119

120+
final Future<ServerSocket> Function(InternetAddress address, int port) _bind;
121+
118122
Future<void> run() async {
119-
final ServerSocket serverSocket = await ServerSocket.bind(InternetAddress.loopbackIPv4, port!);
123+
ServerSocket? serverSocket;
124+
try {
125+
serverSocket = await _bind(InternetAddress.loopbackIPv4, port!);
126+
} on SocketException {
127+
logger.printTrace('Bind on $port failed with IPv4, retrying on IPv6');
128+
}
129+
130+
// If binding on IPv4 failed, try binding on IPv6.
131+
// Omit try catch here, let the failure fallthrough.
132+
serverSocket ??= await _bind(InternetAddress.loopbackIPv6, port!);
133+
120134
logger.printStatus('Daemon server listening on ${serverSocket.port}');
121135

122136
final StreamSubscription<Socket> subscription = serverSocket.listen(
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
// Copyright 2014 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'dart:async';
6+
7+
import 'package:flutter_tools/src/base/io.dart';
8+
import 'package:flutter_tools/src/base/logger.dart';
9+
import 'package:flutter_tools/src/commands/daemon.dart';
10+
import 'package:test/fake.dart';
11+
12+
import '../../src/common.dart';
13+
14+
void main() {
15+
testWithoutContext('binds on ipv4 normally', () async {
16+
final FakeServerSocket socket = FakeServerSocket();
17+
final BufferLogger logger = BufferLogger.test();
18+
19+
int bindCalledTimes = 0;
20+
final List<Object?> bindAddresses = <Object?>[];
21+
final List<int> bindPorts = <int>[];
22+
23+
final DaemonServer server = DaemonServer(
24+
port: 123,
25+
logger: logger,
26+
bind: (Object? address, int port) async {
27+
bindCalledTimes++;
28+
bindAddresses.add(address);
29+
bindPorts.add(port);
30+
return socket;
31+
},
32+
);
33+
await server.run();
34+
expect(bindCalledTimes, 1);
35+
expect(bindAddresses, <Object?>[InternetAddress.loopbackIPv4]);
36+
expect(bindPorts, <int>[123]);
37+
});
38+
39+
testWithoutContext('binds on ipv6 if ipv4 failed normally', () async {
40+
final FakeServerSocket socket = FakeServerSocket();
41+
final BufferLogger logger = BufferLogger.test();
42+
43+
int bindCalledTimes = 0;
44+
final List<Object?> bindAddresses = <Object?>[];
45+
final List<int> bindPorts = <int>[];
46+
47+
final DaemonServer server = DaemonServer(
48+
port: 123,
49+
logger: logger,
50+
bind: (Object? address, int port) async {
51+
bindCalledTimes++;
52+
bindAddresses.add(address);
53+
bindPorts.add(port);
54+
if (address == InternetAddress.loopbackIPv4) {
55+
throw const SocketException('fail');
56+
}
57+
return socket;
58+
},
59+
);
60+
await server.run();
61+
expect(bindCalledTimes, 2);
62+
expect(bindAddresses, <Object?>[InternetAddress.loopbackIPv4, InternetAddress.loopbackIPv6]);
63+
expect(bindPorts, <int>[123, 123]);
64+
});
65+
}
66+
67+
class FakeServerSocket extends Fake implements ServerSocket {
68+
FakeServerSocket();
69+
70+
@override
71+
int get port => 1;
72+
73+
bool closeCalled = false;
74+
final StreamController<Socket> controller = StreamController<Socket>();
75+
76+
@override
77+
StreamSubscription<Socket> listen(
78+
void Function(Socket event)? onData, {
79+
Function? onError,
80+
void Function()? onDone,
81+
bool? cancelOnError,
82+
}) {
83+
// Close the controller immediately for testing purpose.
84+
scheduleMicrotask(() {
85+
controller.close();
86+
});
87+
return controller.stream.listen(onData,
88+
onError: onError, onDone: onDone, cancelOnError: cancelOnError);
89+
}
90+
91+
@override
92+
Future<ServerSocket> close() async {
93+
closeCalled = true;
94+
return this;
95+
}
96+
}

0 commit comments

Comments
 (0)