Unverified Commit c9383913 authored by Jonah Williams's avatar Jonah Williams Committed by GitHub

Fix timing issues in initialization of web resident runner (#41347)

parent 56d68a90
......@@ -4,7 +4,6 @@
import 'dart:async';
import 'package:dwds/dwds.dart';
import 'package:meta/meta.dart';
import 'package:vm_service/vm_service.dart' as vmservice;
......@@ -71,11 +70,11 @@ class ResidentWebRunner extends ResidentRunner {
bool get debuggingEnabled => isRunningDebug && device is! WebServerDevice;
WebFs _webFs;
DebugConnection _debugConnection;
ConnectionResult _connectionResult;
StreamSubscription<vmservice.Event> _stdOutSub;
bool _exited = false;
vmservice.VmService get _vmService => _debugConnection.vmService;
vmservice.VmService get _vmService => _connectionResult?.debugConnection?.vmService;
@override
bool get canHotRestart {
......@@ -105,7 +104,7 @@ class ResidentWebRunner extends ResidentRunner {
if (_exited) {
return;
}
await _debugConnection?.close();
await _connectionResult?.debugConnection?.close();
await _stdOutSub?.cancel();
await _webFs?.stop();
await device.stopApp(null);
......@@ -173,12 +172,25 @@ class ResidentWebRunner extends ResidentRunner {
port: debuggingOptions.port,
skipDwds: device is WebServerDevice,
);
await device.startApp(package, mainPath: target, debuggingOptions: debuggingOptions, platformArgs: <String, Object>{
'uri': _webFs.uri
});
// When connecting to a browser, update the message with a seemsSlow notification
// to handle the case where we fail to connect.
if (debuggingOptions.browserLaunch) {
buildStatus.stop();
buildStatus = logger.startProgress(
'Attempting to connect to browser instance..',
timeout: const Duration(seconds: 30),
);
}
await device.startApp(package,
mainPath: target,
debuggingOptions: debuggingOptions,
platformArgs: <String, Object>{
'uri': _webFs.uri
},
);
if (supportsServiceProtocol) {
_debugConnection = await _webFs.runAndDebug(debuggingOptions);
unawaited(_debugConnection.onDone.whenComplete(exit));
_connectionResult = await _webFs.connect(debuggingOptions);
unawaited(_connectionResult.debugConnection.onDone.whenComplete(exit));
}
} catch (err, stackTrace) {
printError(err.toString());
......@@ -199,26 +211,28 @@ class ResidentWebRunner extends ResidentRunner {
Completer<DebugConnectionInfo> connectionInfoCompleter,
Completer<void> appStartedCompleter,
}) async {
// Cleanup old subscriptions. These will throw if there isn't anything
// listening, which is fine because that is what we want to ensure.
try {
await _debugConnection?.vmService?.streamCancel('Stdout');
} on vmservice.RPCError {
// Ignore this specific error.
}
try {
await _debugConnection?.vmService?.streamListen('Stdout');
} on vmservice.RPCError {
// Ignore this specific error.
}
Uri websocketUri;
if (supportsServiceProtocol) {
_stdOutSub = _debugConnection.vmService.onStdoutEvent.listen((vmservice.Event log) {
// Cleanup old subscriptions. These will throw if there isn't anything
// listening, which is fine because that is what we want to ensure.
try {
await _vmService.streamCancel('Stdout');
} on vmservice.RPCError {
// Ignore this specific error.
}
try {
await _vmService.streamListen('Stdout');
} on vmservice.RPCError {
// Ignore this specific error.
}
_stdOutSub = _vmService.onStdoutEvent.listen((vmservice.Event log) {
final String message = utf8.decode(base64.decode(log.bytes)).trim();
printStatus(message);
});
unawaited(_debugConnection.vmService.registerService('reloadSources', 'FlutterTools'));
websocketUri = Uri.parse(_debugConnection.uri);
unawaited(_vmService.registerService('reloadSources', 'FlutterTools'));
websocketUri = Uri.parse(_connectionResult.debugConnection.uri);
// Always run main after connecting because start paused doesn't work yet.
_connectionResult.appConnection.runMain();
}
if (websocketUri != null) {
printStatus('Debug service listening on $websocketUri');
......
......@@ -112,17 +112,20 @@ class WebFs {
Future<DebugConnection> _cachedExtensionFuture;
/// Retrieve the [DebugConnection] for the current application
Future<DebugConnection> runAndDebug(DebuggingOptions debuggingOptions) {
final Completer<DebugConnection> firstConnection = Completer<DebugConnection>();
/// Connect and retrieve the [DebugConnection] for the current application.
///
/// Only calls [AppConnection.runMain] on the subsequent connections.
Future<ConnectionResult> connect(DebuggingOptions debuggingOptions) {
final Completer<ConnectionResult> firstConnection = Completer<ConnectionResult>();
_connectedApps = _dwds.connectedApps.listen((AppConnection appConnection) async {
final DebugConnection debugConnection = debuggingOptions.browserLaunch
? await _dwds.debugConnection(appConnection)
: await (_cachedExtensionFuture ??= _dwds.extensionDebugConnections.stream.first);
if (!firstConnection.isCompleted) {
firstConnection.complete(debugConnection);
firstConnection.complete(ConnectionResult(appConnection, debugConnection));
} else {
appConnection.runMain();
}
appConnection.runMain();
});
return firstConnection.future;
}
......@@ -393,6 +396,13 @@ class AssetServer {
}
}
class ConnectionResult {
ConnectionResult(this.appConnection, this.debugConnection);
final AppConnection appConnection;
final DebugConnection debugConnection;
}
/// A testable interface for starting a build daemon.
class BuildDaemonCreator {
const BuildDaemonCreator();
......
......@@ -56,7 +56,7 @@ void main() {
fs.file('pubspec.yaml').createSync();
fs.file(fs.path.join('lib', 'main.dart')).createSync(recursive: true);
fs.file(fs.path.join('web', 'index.html')).createSync(recursive: true);
when(mockWebFs.runAndDebug(any)).thenThrow(StateError('debugging not supported'));
when(mockWebFs.connect(any)).thenThrow(StateError('debugging not supported'));
}
test('Can successfully run and connect without vmservice', () => testbed.run(() async {
......
......@@ -3,6 +3,7 @@
// found in the LICENSE file.
import 'dart:async';
import 'dart:convert';
import 'package:dwds/dwds.dart';
import 'package:flutter_tools/src/base/common.dart';
......@@ -31,12 +32,14 @@ void main() {
MockDebugConnection mockDebugConnection;
MockVmService mockVmService;
MockWebDevice mockWebDevice;
MockAppConnection mockAppConnection;
setUp(() {
mockWebFs = MockFlutterWebFs();
mockDebugConnection = MockDebugConnection();
mockVmService = MockVmService();
mockWebDevice = MockWebDevice();
mockAppConnection = MockAppConnection();
testbed = Testbed(
setup: () {
residentWebRunner = ResidentWebRunner(
......@@ -65,8 +68,8 @@ void main() {
fs.file('pubspec.yaml').createSync();
fs.file(fs.path.join('lib', 'main.dart')).createSync(recursive: true);
fs.file(fs.path.join('web', 'index.html')).createSync(recursive: true);
when(mockWebFs.runAndDebug(any)).thenAnswer((Invocation _) async {
return mockDebugConnection;
when(mockWebFs.connect(any)).thenAnswer((Invocation _) async {
return ConnectionResult(mockAppConnection, mockDebugConnection);
});
when(mockWebFs.recompile()).thenAnswer((Invocation _) {
return Future<bool>.value(false);
......@@ -132,11 +135,36 @@ void main() {
));
final DebugConnectionInfo debugConnectionInfo = await connectionInfoCompleter.future;
verify(mockAppConnection.runMain()).called(1);
verify(mockVmService.registerService('reloadSources', 'FlutterTools')).called(1);
expect(bufferLogger.statusText, contains('Debug service listening on ws://127.0.0.1/abcd/'));
expect(debugConnectionInfo.wsUri.toString(), 'ws://127.0.0.1/abcd/');
}));
test('Listens to stdout streams before running main', () => testbed.run(() async {
_setupMocks();
final BufferLogger bufferLogger = logger;
final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>();
final StreamController<Event> controller = StreamController<Event>.broadcast();
when(mockVmService.onStdoutEvent).thenAnswer((Invocation _) {
return controller.stream;
});
when(mockAppConnection.runMain()).thenAnswer((Invocation invocation) {
controller.add(Event.parse(<String, Object>{
'type': 'Event',
'kind': 'WriteEvent',
'timestamp': 1569473488296,
'bytes': base64.encode('THIS MESSAGE IS IMPORTANT'.codeUnits)
}));
});
unawaited(residentWebRunner.run(
connectionInfoCompleter: connectionInfoCompleter,
));
await connectionInfoCompleter.future;
expect(bufferLogger.statusText, contains('THIS MESSAGE IS IMPORTANT'));
}));
test('Can hot reload after attaching', () => testbed.run(() async {
_setupMocks();
final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>();
......@@ -421,4 +449,5 @@ class MockWebDevice extends Mock implements ChromeDevice {}
class MockBuildDaemonCreator extends Mock implements BuildDaemonCreator {}
class MockFlutterWebFs extends Mock implements WebFs {}
class MockDebugConnection extends Mock implements DebugConnection {}
class MockAppConnection extends Mock implements AppConnection {}
class MockVmService extends Mock implements VmService {}
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