Skip to content

Commit 0393341

Browse files
bkonyiflutteractionsbot
authored andcommitted
[Tool] Handle DTD connection failures gracefully in widget-preview (flutter#186952)
Previously, unhandled RpcExceptions and connection-closed StateErrors bubbled up and crashed the "flutter widget-preview start" command and the active file-watching sync streams when the Dart Tooling Daemon (DTD) connection was closed unexpectedly. This change: - Wraps startup DTD calls in a try-catch block and throws a user-friendly ToolExit on failure instead of crashing. - Gracefully catches DTD call exceptions inside the file-watcher stream and logs a warning instead of crashing the active stream. - Adds a unit test case in widget_preview_test.dart to verify that DTD communication failures at startup result in a clean ToolExit.
1 parent 924134a commit 0393341

3 files changed

Lines changed: 64 additions & 8 deletions

File tree

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

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -416,7 +416,20 @@ final class WidgetPreviewStartCommand extends WidgetPreviewSubCommandBase with C
416416
widgetPreviewScaffoldStreamName: _dtdService.widgetPreviewScaffoldStream,
417417
);
418418

419-
final FlutterWidgetPreviews originalPreviews = await _dtdService.getFlutterWidgetPreviews();
419+
final FlutterWidgetPreviews originalPreviews;
420+
try {
421+
originalPreviews = await _dtdService.getFlutterWidgetPreviews();
422+
} on Exception catch (e) {
423+
throwToolExit(
424+
'Failed to retrieve widget previews from the Dart Tooling Daemon (DTD). '
425+
'Ensure that the analysis server is running and reachable. Details: $e',
426+
);
427+
} on StateError catch (e) {
428+
throwToolExit(
429+
'Failed to retrieve widget previews from the Dart Tooling Daemon (DTD). '
430+
'Ensure that the analysis server is running and reachable. Details: $e',
431+
);
432+
}
420433
_previewCodeGenerator.populatePreviewsInGeneratedPreviewScaffoldLsp(originalPreviews);
421434
}
422435

packages/flutter_tools/lib/src/widget_preview/lsp_preview_detector.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,11 @@ class LspPreviewDetector {
184184
} catch (e) {
185185
if (_disposed || shutdownHooks.isShuttingDown) {
186186
logger.printTrace('Failed to get widget previews during shutdown: $e');
187+
} else if (e is StateError || e is Exception) {
188+
logger.printWarning(
189+
'Lost connection to the Dart Tooling Daemon (DTD). '
190+
'Live preview updates are paused. Details: $e',
191+
);
187192
} else {
188193
rethrow;
189194
}

packages/flutter_tools/test/commands.shard/permeable/widget_preview/widget_preview_test.dart

Lines changed: 45 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import 'package:flutter_tools/src/widget_preview/analytics.dart';
2929
import 'package:flutter_tools/src/widget_preview/dtd_services.dart';
3030
import 'package:flutter_tools/src/widget_preview/dtd_types.dart';
3131
import 'package:flutter_tools/src/widget_preview/preview_code_generator.dart';
32+
import 'package:json_rpc_2/json_rpc_2.dart';
3233
import 'package:test/fake.dart';
3334
import 'package:unified_analytics/unified_analytics.dart';
3435

@@ -62,15 +63,20 @@ class FakeWidgetPreviewScaffoldDtdServices extends Fake implements WidgetPreview
6263
Future<void> launchAndConnect({required AnalysisServer analysisServer}) async {}
6364

6465
FlutterWidgetPreviews? nextUpdate;
66+
bool shouldThrow = false;
6567

6668
@override
67-
Future<FlutterWidgetPreviews> getFlutterWidgetPreviews() async =>
68-
nextUpdate ??
69-
const FlutterWidgetPreviews(
70-
namespaces: <String, String>{},
71-
previews: <FlutterWidgetPreviewDetails>[],
72-
scriptUris: <Uri>[],
73-
);
69+
Future<FlutterWidgetPreviews> getFlutterWidgetPreviews() async {
70+
if (shouldThrow) {
71+
throw RpcException(123, 'Fake RPC Exception');
72+
}
73+
return nextUpdate ??
74+
const FlutterWidgetPreviews(
75+
namespaces: <String, String>{},
76+
previews: <FlutterWidgetPreviewDetails>[],
77+
scriptUris: <Uri>[],
78+
);
79+
}
7480
}
7581

7682
class FakeTerminal extends Fake implements Terminal {}
@@ -286,6 +292,38 @@ void main() {
286292

287293
group('flutter widget-preview', () {
288294
group('start exits if', () {
295+
testUsingContext(
296+
'DTD fails to retrieve widget previews',
297+
() async {
298+
final Directory rootProject = await createRootProject();
299+
fakeDtdServices.shouldThrow = true;
300+
try {
301+
await startWidgetPreview(rootProject: rootProject);
302+
fail('Successfully executed despite DTD failure.');
303+
} on ToolExit catch (e) {
304+
expect(
305+
e.message,
306+
contains('Failed to retrieve widget previews from the Dart Tooling Daemon (DTD)'),
307+
);
308+
}
309+
expectNoPreviewLaunchTimingEvents();
310+
},
311+
overrides: <Type, Generator>{
312+
Analytics: () => fakeAnalytics,
313+
DeviceManager: () => fakeDeviceManager,
314+
FileSystem: () => fs,
315+
ProcessManager: () => loggingProcessManager,
316+
Pub: () => Pub.test(
317+
fileSystem: fs,
318+
logger: logger,
319+
processManager: loggingProcessManager,
320+
botDetector: botDetector,
321+
platform: platform,
322+
stdio: mockStdio,
323+
),
324+
},
325+
);
326+
289327
testUsingContext('given an invalid directory', () async {
290328
try {
291329
await runWidgetPreviewCommand(<String>['start', 'foo']);

0 commit comments

Comments
 (0)