Commit acd75c0a authored by John McCutchan's avatar John McCutchan Committed by GitHub

Improve flutter_tools handling of the device being unplugged while syncing DevFS (#9431)

- [x] Catch SocketErrors and handle them gracefully.
- [x] Print 'Lost connection to device' when the service protocol connection is severed unexpectedly.
- [x] Print 'Application finished' when the application exits otherwise.

After this PR:

```
Launching lib/main.dart on Nexus 7 in debug mode...
Running 'gradle assembleDebug'...                     1.2s
Built build/app/outputs/apk/app-debug.apk (21.7MB).
Syncing files to device...
Application finished.
DevFS sync failed. Lost connection to device: SocketException: OS Error: Connection refused, errno = 111, address = 127.0.0.1, port = 53062
Could not perform initial file synchronization.
```

Fixes #6705
parent b645e4ea
......@@ -220,6 +220,13 @@ class ServiceProtocolDevFSOperations implements DevFSOperations {
}
}
class DevFSException implements Exception {
DevFSException(this.message, [this.error, this.stackTrace]);
final String message;
final dynamic error;
final StackTrace stackTrace;
}
class _DevFSHttpWriter {
_DevFSHttpWriter(this.fsName, VMService serviceProtocol)
: httpAddress = serviceProtocol.httpAddress;
......@@ -274,11 +281,16 @@ class _DevFSHttpWriter {
request.headers.removeAll(HttpHeaders.ACCEPT_ENCODING);
request.headers.add('dev_fs_name', fsName);
request.headers.add('dev_fs_uri_b64',
BASE64.encode(UTF8.encode(deviceUri.toString())));
BASE64.encode(UTF8.encode(deviceUri.toString())));
final Stream<List<int>> contents = content.contentsAsCompressedStream();
await request.addStream(contents);
final HttpClientResponse response = await request.close();
await response.drain<Null>();
} on SocketException catch (socketException, stackTrace) {
// We have one completer and can get up to kMaxInFlight errors.
if (!_completer.isCompleted)
_completer.completeError(socketException, stackTrace);
return;
} catch (e) {
if (retry < kMaxRetries) {
printTrace('Retrying writing "$deviceUri" to DevFS due to error: $e');
......@@ -422,8 +434,12 @@ class DevFS {
try {
await _httpWriter.write(dirtyEntries,
progressReporter: progressReporter);
} catch (e) {
printError("Could not update files on device: $e");
} on SocketException catch (socketException, stackTrace) {
printTrace("DevFS sync failed. Lost connection to device: $socketException");
throw new DevFSException('Lost connection to device.', socketException, stackTrace);
} catch (exception, stackTrace) {
printError("Could not update files on device: $exception");
throw new DevFSException('Sync failed', exception, stackTrace);
}
} else {
// Make service protocol requests for each.
......
......@@ -49,6 +49,7 @@ abstract class ResidentRunner {
final bool usesTerminalUI;
final bool stayResident;
final Completer<int> _finished = new Completer<int>();
bool _stopped = false;
String _packagesFilePath;
String get packagesFilePath => _packagesFilePath;
String _projectRootPath;
......@@ -101,6 +102,7 @@ abstract class ResidentRunner {
}
Future<Null> stop() async {
_stopped = true;
await stopEchoingDeviceLog();
await preStop();
return stopApp();
......@@ -259,7 +261,7 @@ abstract class ResidentRunner {
service.done.then<Null>(
_serviceProtocolDone,
onError: _serviceProtocolError
).whenComplete(appFinished);
).whenComplete(_serviceDisconnected);
}
}
......@@ -336,6 +338,18 @@ abstract class ResidentRunner {
}
}
void _serviceDisconnected() {
if (_stopped) {
// User requested the application exit.
return;
}
if (_finished.isCompleted)
return;
printStatus('Lost connection to device.');
_resetTerminal();
_finished.complete(0);
}
void appFinished() {
if (_finished.isCompleted)
return;
......
......@@ -120,7 +120,6 @@ class HotRunner extends ResidentRunner {
}
final bool devfsResult = await _updateDevFS();
if (!devfsResult) {
printError('Could not perform initial file synchronization.');
return 3;
}
......@@ -263,10 +262,16 @@ class HotRunner extends ResidentRunner {
}
final Status devFSStatus = logger.startProgress('Syncing files to device...',
expectSlowOperation: true);
final int bytes = await _devFS.update(progressReporter: progressReporter,
bundle: assetBundle,
bundleDirty: rebuildBundle,
fileFilter: _dartDependencies);
int bytes = 0;
try {
bytes = await _devFS.update(progressReporter: progressReporter,
bundle: assetBundle,
bundleDirty: rebuildBundle,
fileFilter: _dartDependencies);
} on DevFSException {
devFSStatus.cancel();
return false;
}
devFSStatus.stop();
if (!hotRunnerConfig.stableDartDependencies) {
// Clear the set after the sync so they are recomputed next time.
......@@ -323,7 +328,7 @@ class HotRunner extends ResidentRunner {
restartTimer.start();
final bool updatedDevFS = await _updateDevFS();
if (!updatedDevFS)
return new OperationResult(1, 'Dart Source Error');
return new OperationResult(1, 'DevFS Synchronization Failed');
await _launchFromDevFS(package, mainPath);
restartTimer.stop();
printTrace('Restart performed in '
......@@ -413,6 +418,8 @@ class HotRunner extends ResidentRunner {
reassembleTimer = new Stopwatch();
}
final bool updatedDevFS = await _updateDevFS();
if (!updatedDevFS)
return new OperationResult(1, 'DevFS Synchronization Failed');
if (benchmarkMode) {
devFSTimer.stop();
// Record time it took to synchronize to DevFS.
......
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