Unverified Commit 2007186d authored by Zachary Anderson's avatar Zachary Anderson Committed by GitHub

Reland: Handle more cases where the tool receives RPCError 112 (#74602)

* Reland: Handle more cases where the tool receives RPCError 112

* Add null-aware access
parent 6be4d1c8
......@@ -97,19 +97,21 @@ class ScreenshotCommand extends FlutterCommand {
outputFile = globals.fs.file(stringArg(_kOut));
}
bool success = true;
switch (stringArg(_kType)) {
case _kDeviceType:
await runScreenshot(outputFile);
return FlutterCommandResult.success();
break;
case _kSkiaType:
await runSkia(outputFile);
return FlutterCommandResult.success();
success = await runSkia(outputFile);
break;
case _kRasterizerType:
await runRasterizer(outputFile);
return FlutterCommandResult.success();
success = await runRasterizer(outputFile);
break;
}
return FlutterCommandResult.success();
return success ? FlutterCommandResult.success()
: FlutterCommandResult.fail();
}
Future<void> runScreenshot(File outputFile) async {
......@@ -126,10 +128,17 @@ class ScreenshotCommand extends FlutterCommand {
_showOutputFileInfo(outputFile);
}
Future<void> runSkia(File outputFile) async {
Future<bool> runSkia(File outputFile) async {
final Uri observatoryUri = Uri.parse(stringArg(_kObservatoryUri));
final vm_service.VmService vmService = await connectToVmService(observatoryUri);
final vm_service.Response skp = await vmService.screenshotSkp();
if (skp == null) {
globals.printError(
'The Skia picture request failed, probably because the device was '
'disconnected',
);
return false;
}
outputFile ??= globals.fsUtils.getUniqueFile(
globals.fs.currentDirectory,
'flutter',
......@@ -140,12 +149,20 @@ class ScreenshotCommand extends FlutterCommand {
await sink.close();
_showOutputFileInfo(outputFile);
_ensureOutputIsNotJsonRpcError(outputFile);
return true;
}
Future<void> runRasterizer(File outputFile) async {
Future<bool> runRasterizer(File outputFile) async {
final Uri observatoryUri = Uri.parse(stringArg(_kObservatoryUri));
final vm_service.VmService vmService = await connectToVmService(observatoryUri);
final vm_service.Response response = await vmService.screenshot();
if (response == null) {
globals.printError(
'The screenshot request failed, probably because the device was '
'disconnected',
);
return false;
}
outputFile ??= globals.fsUtils.getUniqueFile(
globals.fs.currentDirectory,
'flutter',
......@@ -156,6 +173,7 @@ class ScreenshotCommand extends FlutterCommand {
await sink.close();
_showOutputFileInfo(outputFile);
_ensureOutputIsNotJsonRpcError(outputFile);
return true;
}
void _ensureOutputIsNotJsonRpcError(File outputFile) {
......
......@@ -419,8 +419,15 @@ class DevFS {
final vm_service.Response response = await _vmService.createDevFS(fsName);
_baseUri = Uri.parse(response.json['uri'] as String);
} on vm_service.RPCError catch (rpcException) {
if (rpcException.code == RPCErrorCodes.kServiceDisappeared) {
// This can happen if the device has been disconnected, so translate to
// a DevFSException, which the caller will handle.
throw DevFSException('Service disconnected', rpcException);
}
// 1001 is kFileSystemAlreadyExists in //dart/runtime/vm/json_stream.h
if (rpcException.code != 1001) {
// Other RPCErrors are unexpected. Rethrow so it will hit crash
// logging.
rethrow;
}
_logger.printTrace('DevFS: Creating failed. Destroying and trying again');
......
......@@ -32,7 +32,7 @@ class Tracing {
final Logger _logger;
Future<void> startTracing() async {
await vmService.setVMTimelineFlags(<String>['Compiler', 'Dart', 'Embedder', 'GC']);
await vmService.setTimelineFlags(<String>['Compiler', 'Dart', 'Embedder', 'GC']);
await vmService.clearVMTimeline();
}
......@@ -78,8 +78,13 @@ class Tracing {
}
status.stop();
}
final vm_service.Timeline timeline = await vmService.getVMTimeline();
await vmService.setVMTimelineFlags(<String>[]);
final vm_service.Response timeline = await vmService.getTimeline();
await vmService.setTimelineFlags(<String>[]);
if (timeline == null) {
throwToolExit(
'The device disconnected before the timeline could be retrieved.',
);
}
return timeline.json;
}
}
......
......@@ -728,23 +728,12 @@ extension FlutterVmService on vm_service.VmService {
return null;
}
/// Invoke a flutter extension method, if the flutter extension is not
/// available, returns null.
Future<Map<String, dynamic>> invokeFlutterExtensionRpcRaw(
Future<vm_service.Response> _checkedCallServiceExtension(
String method, {
@required String isolateId,
Map<String, dynamic> args,
}) async {
try {
final vm_service.Response response = await callServiceExtension(
method,
args: <String, Object>{
'isolateId': isolateId,
...?args,
},
);
return response.json;
return await callServiceExtension(method, args: args);
} on vm_service.RPCError catch (err) {
// If an application is not using the framework or the VM service
// disappears while handling a request, return null.
......@@ -756,6 +745,23 @@ extension FlutterVmService on vm_service.VmService {
}
}
/// Invoke a flutter extension method, if the flutter extension is not
/// available, returns null.
Future<Map<String, dynamic>> invokeFlutterExtensionRpcRaw(
String method, {
@required String isolateId,
Map<String, dynamic> args,
}) async {
final vm_service.Response response = await _checkedCallServiceExtension(
method,
args: <String, Object>{
'isolateId': isolateId,
...?args,
},
);
return response?.json;
}
/// List all [FlutterView]s attached to the current VM.
///
/// If this returns an empty list, it will poll forever unless [returnEarly]
......@@ -799,26 +805,34 @@ extension FlutterVmService on vm_service.VmService {
/// Create a new development file system on the device.
Future<vm_service.Response> createDevFS(String fsName) {
return callServiceExtension('_createDevFS', args: <String, dynamic>{'fsName': fsName});
// Call the unchecked version of `callServiceExtension` because the caller
// has custom handling of certain RPCErrors.
return callServiceExtension(
'_createDevFS',
args: <String, dynamic>{'fsName': fsName},
);
}
/// Delete an existing file system.
Future<vm_service.Response> deleteDevFS(String fsName) {
return callServiceExtension('_deleteDevFS', args: <String, dynamic>{'fsName': fsName});
Future<void> deleteDevFS(String fsName) async {
await _checkedCallServiceExtension(
'_deleteDevFS',
args: <String, dynamic>{'fsName': fsName},
);
}
Future<vm_service.Response> screenshot() {
return callServiceExtension(kScreenshotMethod);
return _checkedCallServiceExtension(kScreenshotMethod);
}
Future<vm_service.Response> screenshotSkp() {
return callServiceExtension(kScreenshotSkpMethod);
return _checkedCallServiceExtension(kScreenshotSkpMethod);
}
/// Set the VM timeline flags.
Future<vm_service.Response> setVMTimelineFlags(List<String> recordedStreams) {
Future<void> setTimelineFlags(List<String> recordedStreams) async {
assert(recordedStreams != null);
return callServiceExtension(
await _checkedCallServiceExtension(
'setVMTimelineFlags',
args: <String, dynamic>{
'recordedStreams': recordedStreams,
......@@ -826,8 +840,8 @@ extension FlutterVmService on vm_service.VmService {
);
}
Future<vm_service.Response> getVMTimeline() {
return callServiceExtension('getVMTimeline');
Future<vm_service.Response> getTimeline() {
return _checkedCallServiceExtension('getVMTimeline');
}
}
......
......@@ -31,6 +31,20 @@ final FakeVmServiceRequest createDevFSRequest = FakeVmServiceRequest(
}
);
const FakeVmServiceRequest failingCreateDevFSRequest = FakeVmServiceRequest(
method: '_createDevFS',
args: <String, Object>{
'fsName': 'test',
},
errorCode: RPCErrorCodes.kServiceDisappeared,
);
const FakeVmServiceRequest failingDeleteDevFSRequest = FakeVmServiceRequest(
method: '_deleteDevFS',
args: <String, dynamic>{'fsName': 'test'},
errorCode: RPCErrorCodes.kServiceDisappeared,
);
void main() {
testWithoutContext('DevFSByteContent', () {
final DevFSByteContent content = DevFSByteContent(<int>[4, 5, 6]);
......@@ -93,6 +107,73 @@ void main() {
expect(content.isModified, isFalse);
});
testWithoutContext('DevFS create throws a DevFSException when vmservice disconnects unexpectedly', () async {
final HttpClient httpClient = MockHttpClient();
final FileSystem fileSystem = MemoryFileSystem.test();
final OperatingSystemUtils osUtils = MockOperatingSystemUtils();
final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(
requests: <VmServiceExpectation>[failingCreateDevFSRequest],
);
setHttpAddress(Uri.parse('http://localhost'), fakeVmServiceHost.vmService);
final MockHttpClientRequest httpRequest = MockHttpClientRequest();
when(httpRequest.headers).thenReturn(MockHttpHeaders());
when(httpClient.putUrl(any)).thenAnswer((Invocation invocation) {
return Future<HttpClientRequest>.value(httpRequest);
});
final MockHttpClientResponse httpClientResponse = MockHttpClientResponse();
when(httpRequest.close()).thenAnswer((Invocation invocation) {
return Future<HttpClientResponse>.value(httpClientResponse);
});
final DevFS devFS = DevFS(
fakeVmServiceHost.vmService,
'test',
fileSystem.currentDirectory,
osUtils: osUtils,
fileSystem: fileSystem,
logger: BufferLogger.test(),
httpClient: httpClient,
);
expect(() async => await devFS.create(), throwsA(isA<DevFSException>()));
});
testWithoutContext('DevFS destroy is resiliant to vmservice disconnection', () async {
final HttpClient httpClient = MockHttpClient();
final FileSystem fileSystem = MemoryFileSystem.test();
final OperatingSystemUtils osUtils = MockOperatingSystemUtils();
final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(
requests: <VmServiceExpectation>[
createDevFSRequest,
failingDeleteDevFSRequest,
],
);
setHttpAddress(Uri.parse('http://localhost'), fakeVmServiceHost.vmService);
final MockHttpClientRequest httpRequest = MockHttpClientRequest();
when(httpRequest.headers).thenReturn(MockHttpHeaders());
when(httpClient.putUrl(any)).thenAnswer((Invocation invocation) {
return Future<HttpClientRequest>.value(httpRequest);
});
final MockHttpClientResponse httpClientResponse = MockHttpClientResponse();
when(httpRequest.close()).thenAnswer((Invocation invocation) {
return Future<HttpClientResponse>.value(httpClientResponse);
});
final DevFS devFS = DevFS(
fakeVmServiceHost.vmService,
'test',
fileSystem.currentDirectory,
osUtils: osUtils,
fileSystem: fileSystem,
logger: BufferLogger.test(),
httpClient: httpClient,
);
expect(await devFS.create(), isNotNull);
await devFS.destroy(); // Testing that this does not throw.
});
testWithoutContext('DevFS retries uploads when connection reset by peer', () async {
final HttpClient httpClient = MockHttpClient();
final FileSystem fileSystem = MemoryFileSystem.test();
......
......@@ -130,6 +130,29 @@ void main() {
});
});
testWithoutContext('throws tool exit if the vmservice disconnects', () async {
final BufferLogger logger = BufferLogger.test();
final FileSystem fileSystem = MemoryFileSystem.test();
final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(requests: <FakeVmServiceRequest>[
...vmServiceSetup,
const FakeVmServiceRequest(
method: 'getVMTimeline',
errorCode: RPCErrorCodes.kServiceDisappeared,
),
const FakeVmServiceRequest(
method: 'setVMTimelineFlags',
args: <String, Object>{
'recordedStreams': <Object>[],
},
),
]);
await expectLater(() async => await downloadStartupTrace(fakeVmServiceHost.vmService,
output: fileSystem.currentDirectory,
logger: logger,
), throwsToolExit(message: 'The device disconnected before the timeline could be retrieved.'));
});
testWithoutContext('throws tool exit if timeline is missing the engine start event', () async {
final BufferLogger logger = BufferLogger.test();
final FileSystem fileSystem = MemoryFileSystem.test();
......
......@@ -315,20 +315,58 @@ void main() {
testWithoutContext('Framework service extension invocations return null if service disappears ', () async {
final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(
requests: <VmServiceExpectation>[
const FakeVmServiceRequest(method: kGetSkSLsMethod, args: <String, Object>{
'viewId': '1234',
}, errorCode: RPCErrorCodes.kServiceDisappeared),
const FakeVmServiceRequest(method: kListViewsMethod, errorCode: RPCErrorCodes.kServiceDisappeared),
const FakeVmServiceRequest(
method: kGetSkSLsMethod,
args: <String, Object>{
'viewId': '1234',
},
errorCode: RPCErrorCodes.kServiceDisappeared,
),
const FakeVmServiceRequest(
method: kListViewsMethod,
errorCode: RPCErrorCodes.kServiceDisappeared,
),
const FakeVmServiceRequest(
method: kScreenshotMethod,
errorCode: RPCErrorCodes.kServiceDisappeared,
),
const FakeVmServiceRequest(
method: kScreenshotSkpMethod,
errorCode: RPCErrorCodes.kServiceDisappeared,
),
const FakeVmServiceRequest(
method: 'setVMTimelineFlags',
args: <String, dynamic>{
'recordedStreams': <String>['test'],
},
errorCode: RPCErrorCodes.kServiceDisappeared,
),
const FakeVmServiceRequest(
method: 'getVMTimeline',
errorCode: RPCErrorCodes.kServiceDisappeared,
),
]
);
final Map<String, Object> skSLs = await fakeVmServiceHost.vmService.getSkSLs(
viewId: '1234',
);
expect(skSLs, null);
expect(skSLs, isNull);
final List<FlutterView> views = await fakeVmServiceHost.vmService.getFlutterViews();
expect(views, null);
expect(views, isNull);
final vm_service.Response screenshot = await fakeVmServiceHost.vmService.screenshot();
expect(screenshot, isNull);
final vm_service.Response screenshotSkp = await fakeVmServiceHost.vmService.screenshotSkp();
expect(screenshotSkp, isNull);
// Checking that this doesn't throw.
await fakeVmServiceHost.vmService.setTimelineFlags(<String>['test']);
final vm_service.Response timeline = await fakeVmServiceHost.vmService.getTimeline();
expect(timeline, isNull);
expect(fakeVmServiceHost.hasRemainingExpectations, false);
});
......
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