Skip to content

Commit 5d11325

Browse files
authored
Add debugging for Dart VM timeout flake (#126437)
Check what is available in the device's iOS DeviceSupport folder to check if symbols were properly fetched. Also, add some logging to track what status the debugger is in. Debugging for flutter/flutter#121231.
1 parent 0636f5e commit 5d11325

File tree

3 files changed

+138
-0
lines changed

3 files changed

+138
-0
lines changed

packages/flutter_tools/lib/src/ios/devices.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -546,6 +546,7 @@ class IOSDevice extends Device {
546546
"If you don't see your app in the Settings, uninstall the app and rerun to see the prompt again."
547547
);
548548
} else {
549+
iosDeployDebugger?.checkForSymbolsFiles(_fileSystem);
549550
iosDeployDebugger?.pauseDumpBacktraceResume();
550551
}
551552
});

packages/flutter_tools/lib/src/ios/ios_deploy.dart

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,9 @@ class IOSDeployDebugger {
288288
bool get debuggerAttached => _debuggerState == _IOSDeployDebuggerState.attached;
289289
_IOSDeployDebuggerState _debuggerState;
290290

291+
@visibleForTesting
292+
String? symbolsDirectoryPath;
293+
291294
// (lldb) platform select remote-'ios' --sysroot
292295
// https://github.com/ios-control/ios-deploy/blob/1.11.2-beta.1/src/ios-deploy/ios-deploy.m#L33
293296
// This regex is to get the configurable lldb prompt. By default this prompt will be "lldb".
@@ -306,6 +309,9 @@ class IOSDeployDebugger {
306309
// (lldb) Process 6152 resuming
307310
static final RegExp _lldbProcessResuming = RegExp(r'Process \d+ resuming');
308311

312+
// Symbol Path: /Users/swarming/Library/Developer/Xcode/iOS DeviceSupport/16.2 (20C65) arm64e/Symbols
313+
static final RegExp _symbolsPathPattern = RegExp(r'.*Symbol Path: ');
314+
309315
// Send signal to stop (pause) the app. Used before a backtrace dump.
310316
static const String _signalStop = 'process signal SIGSTOP';
311317

@@ -362,12 +368,25 @@ class IOSDeployDebugger {
362368
return;
363369
}
364370

371+
// Symbol Path: /Users/swarming/Library/Developer/Xcode/iOS DeviceSupport/16.2 (20C65) arm64e/Symbols
372+
if (_symbolsPathPattern.hasMatch(line)) {
373+
_logger.printTrace('Detected path to iOS debug symbols: "$line"');
374+
final String prefix = _symbolsPathPattern.stringMatch(line) ?? '';
375+
if (prefix.isEmpty) {
376+
return;
377+
}
378+
symbolsDirectoryPath = line.substring(prefix.length);
379+
return;
380+
}
381+
365382
// (lldb) run
366383
// success
367384
// 2020-09-15 13:42:25.185474-0700 Runner[477:181141] flutter: The Dart VM service is listening on http://127.0.0.1:57782/
368385
if (lldbRun.hasMatch(line)) {
369386
_logger.printTrace(line);
370387
_debuggerState = _IOSDeployDebuggerState.launching;
388+
// TODO(vashworth): Remove all debugger state comments when https://github.com/flutter/flutter/issues/126412 is resolved.
389+
_logger.printTrace('Debugger state set to launching.');
371390
return;
372391
}
373392
// Next line after "run" must be "success", or the attach failed.
@@ -376,6 +395,7 @@ class IOSDeployDebugger {
376395
_logger.printTrace(line);
377396
final bool attachSuccess = line == 'success';
378397
_debuggerState = attachSuccess ? _IOSDeployDebuggerState.attached : _IOSDeployDebuggerState.detached;
398+
_logger.printTrace('Debugger state set to ${attachSuccess ? 'attached' : 'detached'}.');
379399
if (!debuggerCompleter.isCompleted) {
380400
debuggerCompleter.complete(attachSuccess);
381401
}
@@ -392,6 +412,7 @@ class IOSDeployDebugger {
392412
// Even though we're not "detached", just stopped, mark as detached so the backtrace
393413
// is only show in verbose.
394414
_debuggerState = _IOSDeployDebuggerState.detached;
415+
_logger.printTrace('Debugger state set to detached.');
395416

396417
// If we paused the app and are waiting to resume it, complete the completer
397418
final Completer<void>? processResumeCompleter = _processResumeCompleter;
@@ -422,6 +443,7 @@ class IOSDeployDebugger {
422443
if (_lldbProcessDetached.hasMatch(line)) {
423444
// The debugger has detached from the app, and there will be no more debugging messages.
424445
// Kill the ios-deploy process.
446+
_logger.printTrace(line);
425447
exit();
426448
return;
427449
}
@@ -430,6 +452,7 @@ class IOSDeployDebugger {
430452
_logger.printTrace(line);
431453
// we marked this detached when we received [_backTraceAll]
432454
_debuggerState = _IOSDeployDebuggerState.attached;
455+
_logger.printTrace('Debugger state set to attached.');
433456
return;
434457
}
435458

@@ -510,6 +533,33 @@ class IOSDeployDebugger {
510533
_iosDeployProcess?.stdin.writeln(_processResume);
511534
}
512535

536+
/// Check what files are found in the device's iOS DeviceSupport directory.
537+
///
538+
/// Expected files include Symbols (directory), Info.plist, and .finalized.
539+
///
540+
/// If any of the expected files are missing or there are additional files
541+
/// (such as .copying_lock or .processing_lock), this may indicate the
542+
/// symbols may still be fetching or something went wrong when fetching them.
543+
///
544+
/// Used for debugging test flakes: https://github.com/flutter/flutter/issues/121231
545+
Future<void> checkForSymbolsFiles(FileSystem fileSystem) async {
546+
if (symbolsDirectoryPath == null) {
547+
_logger.printTrace('No path provided for Symbols directory.');
548+
return;
549+
}
550+
final Directory symbolsDirectory = fileSystem.directory(symbolsDirectoryPath);
551+
if (!symbolsDirectory.existsSync()) {
552+
_logger.printTrace('Unable to find Symbols directory at $symbolsDirectoryPath');
553+
return;
554+
}
555+
final Directory currentDeviceSupportDir = symbolsDirectory.parent;
556+
final List<FileSystemEntity> symbolStatusFiles = currentDeviceSupportDir.listSync();
557+
_logger.printTrace('Symbol files:');
558+
for (final FileSystemEntity file in symbolStatusFiles) {
559+
_logger.printTrace(' ${file.basename}');
560+
}
561+
}
562+
513563
Future<void> stopAndDumpBacktrace() async {
514564
if (!debuggerAttached) {
515565
return;

packages/flutter_tools/test/general.shard/ios/ios_deploy_test.dart

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,93 @@ process continue
428428
'process detach',
429429
]);
430430
});
431+
432+
group('Check for symbols', () {
433+
late String symbolsDirectoryPath;
434+
435+
setUp(() {
436+
fileSystem = MemoryFileSystem.test();
437+
symbolsDirectoryPath = '/Users/swarming/Library/Developer/Xcode/iOS DeviceSupport/16.2 (20C65) arm64e/Symbols';
438+
});
439+
440+
testWithoutContext('and no path provided', () async {
441+
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
442+
const FakeCommand(
443+
command: <String>[
444+
'ios-deploy',
445+
],
446+
stdout:
447+
'(lldb) Process 6156 stopped',
448+
),
449+
]);
450+
final BufferLogger logger = BufferLogger.test();
451+
final IOSDeployDebugger iosDeployDebugger = IOSDeployDebugger.test(
452+
processManager: processManager,
453+
logger: logger,
454+
);
455+
await iosDeployDebugger.launchAndAttach();
456+
await iosDeployDebugger.checkForSymbolsFiles(fileSystem);
457+
expect(iosDeployDebugger.symbolsDirectoryPath, isNull);
458+
expect(logger.traceText, contains('No path provided for Symbols directory.'));
459+
});
460+
461+
testWithoutContext('and unable to find directory', () async {
462+
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
463+
FakeCommand(
464+
command: const <String>[
465+
'ios-deploy',
466+
],
467+
stdout:
468+
'[ 95%] Developer disk image mounted successfully\n'
469+
'Symbol Path: $symbolsDirectoryPath\n'
470+
'[100%] Connecting to remote debug server',
471+
),
472+
]);
473+
final BufferLogger logger = BufferLogger.test();
474+
final IOSDeployDebugger iosDeployDebugger = IOSDeployDebugger.test(
475+
processManager: processManager,
476+
logger: logger,
477+
);
478+
await iosDeployDebugger.launchAndAttach();
479+
await iosDeployDebugger.checkForSymbolsFiles(fileSystem);
480+
expect(iosDeployDebugger.symbolsDirectoryPath, symbolsDirectoryPath);
481+
expect(logger.traceText, contains('Unable to find Symbols directory'));
482+
});
483+
484+
testWithoutContext('and find status', () async {
485+
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
486+
FakeCommand(
487+
command: const <String>[
488+
'ios-deploy',
489+
],
490+
stdout:
491+
'[ 95%] Developer disk image mounted successfully\n'
492+
'Symbol Path: $symbolsDirectoryPath\n'
493+
'[100%] Connecting to remote debug server',
494+
),
495+
]);
496+
final BufferLogger logger = BufferLogger.test();
497+
final IOSDeployDebugger iosDeployDebugger = IOSDeployDebugger.test(
498+
processManager: processManager,
499+
logger: logger,
500+
);
501+
final Directory symbolsDirectory = fileSystem.directory(symbolsDirectoryPath);
502+
symbolsDirectory.createSync(recursive: true);
503+
504+
final File copyingStatusFile = symbolsDirectory.parent.childFile('.copying_lock');
505+
copyingStatusFile.createSync();
506+
507+
final File processingStatusFile = symbolsDirectory.parent.childFile('.processing_lock');
508+
processingStatusFile.createSync();
509+
510+
await iosDeployDebugger.launchAndAttach();
511+
await iosDeployDebugger.checkForSymbolsFiles(fileSystem);
512+
expect(iosDeployDebugger.symbolsDirectoryPath, symbolsDirectoryPath);
513+
expect(logger.traceText, contains('Symbol files:'));
514+
expect(logger.traceText, contains('.copying_lock'));
515+
expect(logger.traceText, contains('.processing_lock'));
516+
});
517+
});
431518
});
432519

433520
group('IOSDeploy.uninstallApp', () {

0 commit comments

Comments
 (0)