Skip to content

Commit f89b4f1

Browse files
[flutter_tools] Catch rpc error in render frame with raster stats (#144190)
Fixes flutter/flutter#143010. This is intended to be cherrypicked into the 3.19 and 3.20 releases. Long-term, we should deprecate this feature: flutter/flutter#144191
1 parent 47b0ef8 commit f89b4f1

10 files changed

+109
-50
lines changed

packages/flutter_tools/lib/src/resident_runner.dart

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -714,23 +714,30 @@ abstract class ResidentHandlers {
714714
continue;
715715
}
716716
final List<FlutterView> views = await device!.vmService!.getFlutterViews();
717-
for (final FlutterView view in views) {
718-
final Map<String, Object?>? rasterData =
719-
await device.vmService!.renderFrameWithRasterStats(
720-
viewId: view.id,
721-
uiIsolateId: view.uiIsolate!.id,
722-
);
723-
if (rasterData != null) {
724-
final File tempFile = globals.fsUtils.getUniqueFile(
725-
globals.fs.currentDirectory,
726-
'flutter_jank_metrics',
727-
'json',
728-
);
729-
tempFile.writeAsStringSync(jsonEncode(rasterData), flush: true);
730-
logger.printStatus('Wrote jank metrics to ${tempFile.absolute.path}');
731-
} else {
732-
logger.printWarning('Unable to get jank metrics.');
717+
try {
718+
for (final FlutterView view in views) {
719+
final Map<String, Object?>? rasterData =
720+
await device.vmService!.renderFrameWithRasterStats(
721+
viewId: view.id,
722+
uiIsolateId: view.uiIsolate!.id,
723+
);
724+
if (rasterData != null) {
725+
final File tempFile = globals.fsUtils.getUniqueFile(
726+
globals.fs.currentDirectory,
727+
'flutter_jank_metrics',
728+
'json',
729+
);
730+
tempFile.writeAsStringSync(jsonEncode(rasterData), flush: true);
731+
logger.printStatus('Wrote jank metrics to ${tempFile.absolute.path}');
732+
} else {
733+
logger.printWarning('Unable to get jank metrics.');
734+
}
735+
}
736+
} on vm_service.RPCError catch (err) {
737+
if (err.code != RPCErrorCodes.kServerError || !err.message.contains('Raster status not supported on Impeller backend')) {
738+
rethrow;
733739
}
740+
logger.printWarning('Unable to get jank metrics for Impeller renderer');
734741
}
735742
}
736743
return true;

packages/flutter_tools/test/general.shard/devfs_test.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,13 @@ const FakeVmServiceRequest failingCreateDevFSRequest = FakeVmServiceRequest(
4848
args: <String, Object>{
4949
'fsName': 'test',
5050
},
51-
errorCode: RPCErrorCodes.kServiceDisappeared,
51+
error: FakeRPCError(code: RPCErrorCodes.kServiceDisappeared),
5252
);
5353

5454
const FakeVmServiceRequest failingDeleteDevFSRequest = FakeVmServiceRequest(
5555
method: '_deleteDevFS',
5656
args: <String, dynamic>{'fsName': 'test'},
57-
errorCode: RPCErrorCodes.kServiceDisappeared,
57+
error: FakeRPCError(code: RPCErrorCodes.kServiceDisappeared),
5858
);
5959

6060
void main() {

packages/flutter_tools/test/general.shard/resident_devtools_handler_test.dart

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -264,14 +264,14 @@ void main() {
264264
),
265265
const FakeVmServiceRequest(
266266
method: kListViewsMethod,
267-
errorCode: RPCErrorCodes.kServiceDisappeared,
267+
error: FakeRPCError(code: RPCErrorCodes.kServiceDisappeared),
268268
),
269269
const FakeVmServiceRequest(
270270
method: 'streamCancel',
271271
args: <String, Object>{
272272
'streamId': 'Isolate',
273273
},
274-
errorCode: RPCErrorCodes.kServiceDisappeared,
274+
error: FakeRPCError(code: RPCErrorCodes.kServiceDisappeared),
275275
),
276276
], httpAddress: Uri.parse('http://localhost:1234'));
277277

@@ -340,14 +340,14 @@ void main() {
340340
),
341341
const FakeVmServiceRequest(
342342
method: kListViewsMethod,
343-
errorCode: RPCErrorCodes.kServiceDisappeared,
343+
error: FakeRPCError(code: RPCErrorCodes.kServiceDisappeared),
344344
),
345345
const FakeVmServiceRequest(
346346
method: 'streamCancel',
347347
args: <String, Object>{
348348
'streamId': 'Isolate',
349349
},
350-
errorCode: RPCErrorCodes.kServiceDisappeared,
350+
error: FakeRPCError(code: RPCErrorCodes.kServiceDisappeared),
351351
),
352352
], httpAddress: Uri.parse('http://localhost:5678'));
353353

packages/flutter_tools/test/general.shard/resident_runner_helpers.dart

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,18 @@ final FakeVmServiceRequest listViews = FakeVmServiceRequest(
114114
},
115115
);
116116

117+
const FakeVmServiceRequest renderFrameRasterStats = FakeVmServiceRequest(
118+
method: kRenderFrameWithRasterStatsMethod,
119+
args: <String, Object>{
120+
'viewId': 'a',
121+
'isolateId': '1',
122+
},
123+
error: FakeRPCError(
124+
code: RPCErrorCodes.kServerError,
125+
error: 'Raster status not supported on Impeller backend',
126+
),
127+
);
128+
117129
const FakeVmServiceRequest setAssetBundlePath = FakeVmServiceRequest(
118130
method: '_flutter.setAssetBundlePath',
119131
args: <String, Object>{

packages/flutter_tools/test/general.shard/resident_runner_test.dart

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,32 @@ void main() {
333333
Usage: () => TestUsage(),
334334
}));
335335

336+
testUsingContext('ResidentRunner can handle an RPC exception from debugFrameJankMetrics', () => testbed.run(() async {
337+
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
338+
listViews,
339+
listViews,
340+
listViews,
341+
renderFrameRasterStats,
342+
]);
343+
final Completer<DebugConnectionInfo> futureConnectionInfo = Completer<DebugConnectionInfo>.sync();
344+
final Completer<void> futureAppStart = Completer<void>.sync();
345+
unawaited(residentRunner.attach(
346+
appStartedCompleter: futureAppStart,
347+
connectionInfoCompleter: futureConnectionInfo,
348+
enableDevTools: true,
349+
));
350+
await futureAppStart.future;
351+
352+
final bool result = await residentRunner.debugFrameJankMetrics();
353+
expect(result, true);
354+
expect((globals.flutterUsage as TestUsage).events, isEmpty);
355+
expect(fakeAnalytics.sentEvents, isEmpty);
356+
expect(fakeVmServiceHost?.hasRemainingExpectations, false);
357+
expect((globals.logger as BufferLogger).warningText, contains('Unable to get jank metrics for Impeller renderer'));
358+
}, overrides: <Type, Generator>{
359+
Usage: () => TestUsage(),
360+
}));
361+
336362
testUsingContext('ResidentRunner fails its operation if the device initialization is not complete', () => testbed.run(() async {
337363
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
338364
listViews,

packages/flutter_tools/test/general.shard/resident_web_runner_test.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -973,7 +973,7 @@ void main() {
973973
const FakeVmServiceRequest(
974974
method: kHotRestartServiceName,
975975
// Failed response,
976-
errorCode: RPCErrorCodes.kInternalError,
976+
error: FakeRPCError(code: RPCErrorCodes.kInternalError),
977977
),
978978
]);
979979
setupMocks();

packages/flutter_tools/test/general.shard/terminal_handler_test.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1125,7 +1125,7 @@ void main() {
11251125
const FakeVmServiceRequest(
11261126
method: 'ext.dwds.screenshot',
11271127
// Failed response,
1128-
errorCode: RPCErrorCodes.kInternalError,
1128+
error: FakeRPCError(code: RPCErrorCodes.kInternalError),
11291129
),
11301130
FakeVmServiceRequest(
11311131
method: 'ext.flutter.debugAllowBanner',
@@ -1165,7 +1165,7 @@ void main() {
11651165
'enabled': 'true',
11661166
},
11671167
// Failed response,
1168-
errorCode: RPCErrorCodes.kInternalError,
1168+
error: const FakeRPCError(code: RPCErrorCodes.kInternalError),
11691169
),
11701170
],
11711171
logger: logger,

packages/flutter_tools/test/general.shard/tracing_test.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ void main() {
140140
...vmServiceSetup,
141141
const FakeVmServiceRequest(
142142
method: 'getVMTimeline',
143-
errorCode: RPCErrorCodes.kServiceDisappeared,
143+
error: FakeRPCError(code: RPCErrorCodes.kServiceDisappeared),
144144
),
145145
const FakeVmServiceRequest(
146146
method: 'setVMTimelineFlags',

packages/flutter_tools/test/general.shard/vmservice_test.dart

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -301,7 +301,7 @@ void main() {
301301
args: <String, Object>{
302302
'isolateId': '1',
303303
},
304-
errorCode: RPCErrorCodes.kMethodNotFound,
304+
error: FakeRPCError(code: RPCErrorCodes.kMethodNotFound),
305305
),
306306
]
307307
);
@@ -320,7 +320,7 @@ void main() {
320320
args: <String, Object>{
321321
'isolateId': '1',
322322
},
323-
errorCode: RPCErrorCodes.kMethodNotFound,
323+
error: FakeRPCError(code: RPCErrorCodes.kMethodNotFound),
324324
),
325325
]
326326
);
@@ -339,7 +339,7 @@ void main() {
339339
args: <String, Object>{
340340
'isolateId': '1',
341341
},
342-
errorCode: RPCErrorCodes.kMethodNotFound,
342+
error: FakeRPCError(code: RPCErrorCodes.kMethodNotFound),
343343
),
344344
]
345345
);
@@ -358,7 +358,7 @@ void main() {
358358
args: <String, Object>{
359359
'isolateId': '1',
360360
},
361-
errorCode: RPCErrorCodes.kMethodNotFound,
361+
error: FakeRPCError(code: RPCErrorCodes.kMethodNotFound),
362362
),
363363
]
364364
);
@@ -377,7 +377,7 @@ void main() {
377377
args: <String, Object>{
378378
'isolateId': '1',
379379
},
380-
errorCode: RPCErrorCodes.kMethodNotFound,
380+
error: FakeRPCError(code: RPCErrorCodes.kMethodNotFound),
381381
),
382382
]
383383
);
@@ -396,7 +396,7 @@ void main() {
396396
args: <String, Object>{
397397
'isolateId': '1',
398398
},
399-
errorCode: RPCErrorCodes.kMethodNotFound,
399+
error: FakeRPCError(code: RPCErrorCodes.kMethodNotFound),
400400
),
401401
]
402402
);
@@ -436,34 +436,34 @@ void main() {
436436
args: <String, Object>{
437437
'viewId': '1234',
438438
},
439-
errorCode: RPCErrorCodes.kServiceDisappeared,
439+
error: FakeRPCError(code: RPCErrorCodes.kServiceDisappeared),
440440
),
441441
const FakeVmServiceRequest(
442442
method: kListViewsMethod,
443-
errorCode: RPCErrorCodes.kServiceDisappeared,
443+
error: FakeRPCError(code: RPCErrorCodes.kServiceDisappeared),
444444
),
445445
const FakeVmServiceRequest(
446446
method: kScreenshotSkpMethod,
447-
errorCode: RPCErrorCodes.kServiceDisappeared,
447+
error: FakeRPCError(code: RPCErrorCodes.kServiceDisappeared),
448448
),
449449
const FakeVmServiceRequest(
450450
method: 'setVMTimelineFlags',
451451
args: <String, dynamic>{
452452
'recordedStreams': <String>['test'],
453453
},
454-
errorCode: RPCErrorCodes.kServiceDisappeared,
454+
error: FakeRPCError(code: RPCErrorCodes.kServiceDisappeared),
455455
),
456456
const FakeVmServiceRequest(
457457
method: 'getVMTimeline',
458-
errorCode: RPCErrorCodes.kServiceDisappeared,
458+
error: FakeRPCError(code: RPCErrorCodes.kServiceDisappeared),
459459
),
460460
const FakeVmServiceRequest(
461461
method: kRenderFrameWithRasterStatsMethod,
462462
args: <String, dynamic>{
463463
'viewId': '1',
464464
'isolateId': '12',
465465
},
466-
errorCode: RPCErrorCodes.kServiceDisappeared,
466+
error: FakeRPCError(code: RPCErrorCodes.kServiceDisappeared),
467467
),
468468
]
469469
);
@@ -495,9 +495,13 @@ void main() {
495495
testWithoutContext('getIsolateOrNull returns null if service disappears ', () async {
496496
final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(
497497
requests: <VmServiceExpectation>[
498-
const FakeVmServiceRequest(method: 'getIsolate', args: <String, Object>{
499-
'isolateId': 'isolate/123',
500-
}, errorCode: RPCErrorCodes.kServiceDisappeared),
498+
const FakeVmServiceRequest(
499+
method: 'getIsolate',
500+
args: <String, Object>{
501+
'isolateId': 'isolate/123',
502+
},
503+
error: FakeRPCError(code: RPCErrorCodes.kServiceDisappeared),
504+
),
501505
]
502506
);
503507

@@ -702,7 +706,7 @@ void main() {
702706
args: <String, Object>{
703707
'isolateId': '1',
704708
},
705-
errorCode: RPCErrorCodes.kServiceDisappeared,
709+
error: FakeRPCError(code: RPCErrorCodes.kServiceDisappeared),
706710
),
707711
// Assume a different isolate returns.
708712
FakeVmServiceStreamResponse(
@@ -734,7 +738,7 @@ void main() {
734738
'streamId': 'Isolate',
735739
},
736740
// Stream already subscribed - https://github.com/dart-lang/sdk/blob/main/runtime/vm/service/service.md#streamlisten
737-
errorCode: 103,
741+
error: FakeRPCError(code: 103),
738742
),
739743
listViewsRequest,
740744
FakeVmServiceRequest(
@@ -802,14 +806,14 @@ void main() {
802806
),
803807
const FakeVmServiceRequest(
804808
method: kListViewsMethod,
805-
errorCode: RPCErrorCodes.kServiceDisappeared,
809+
error: FakeRPCError(code: RPCErrorCodes.kServiceDisappeared),
806810
),
807811
const FakeVmServiceRequest(
808812
method: 'streamCancel',
809813
args: <String, Object>{
810814
'streamId': 'Isolate',
811815
},
812-
errorCode: RPCErrorCodes.kServiceDisappeared,
816+
error: FakeRPCError(code: RPCErrorCodes.kServiceDisappeared),
813817
),
814818
]);
815819

packages/flutter_tools/test/src/fake_vm_services.dart

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ class FakeVmServiceHost {
3939
expect(_requests, isEmpty);
4040
return;
4141
}
42-
if (fakeRequest.errorCode == null) {
42+
if (fakeRequest.error == null) {
4343
_input.add(json.encode(<String, Object?>{
4444
'jsonrpc': '2.0',
4545
'id': request['id'],
@@ -50,8 +50,8 @@ class FakeVmServiceHost {
5050
'jsonrpc': '2.0',
5151
'id': request['id'],
5252
'error': <String, Object?>{
53-
'code': fakeRequest.errorCode,
54-
'message': 'error',
53+
'code': fakeRequest.error!.code,
54+
'message': fakeRequest.error!.error,
5555
},
5656
}));
5757
}
@@ -90,12 +90,22 @@ abstract class VmServiceExpectation {
9090
bool get isRequest;
9191
}
9292

93+
class FakeRPCError {
94+
const FakeRPCError({
95+
required this.code,
96+
this.error = 'error',
97+
});
98+
99+
final int code;
100+
final String error;
101+
}
102+
93103
class FakeVmServiceRequest implements VmServiceExpectation {
94104
const FakeVmServiceRequest({
95105
required this.method,
96106
this.args = const <String, Object?>{},
97107
this.jsonResponse,
98-
this.errorCode,
108+
this.error,
99109
this.close = false,
100110
});
101111

@@ -106,7 +116,7 @@ class FakeVmServiceRequest implements VmServiceExpectation {
106116

107117
/// If non-null, the error code for a [vm_service.RPCError] in place of a
108118
/// standard response.
109-
final int? errorCode;
119+
final FakeRPCError? error;
110120
final Map<String, Object?>? args;
111121
final Map<String, Object?>? jsonResponse;
112122

0 commit comments

Comments
 (0)