Commit cf96c7db authored by Chris Bracken's avatar Chris Bracken Committed by GitHub

Clean up orphaned Instruments processes (#11189)

In some cases, we've seen interactions between Instruments and the iOS
simulator that cause hung instruments and DTServiceHub processes. If
enough instances pile up, the host machine eventually becomes
unresponsive.

Until the underlying issue is resolved, manually kill any orphaned
instances (where the parent process has died and PPID is 1) before
launching another instruments run.
parent bde8a5f5
...@@ -165,7 +165,37 @@ class Xcode { ...@@ -165,7 +165,37 @@ class Xcode {
return _xcodeVersionCheckValid(xcodeMajorVersion, xcodeMinorVersion); return _xcodeVersionCheckValid(xcodeMajorVersion, xcodeMinorVersion);
} }
final RegExp _processRegExp = new RegExp(r'^(\S+)\s+1\s+(\d+)\s+(.+)$');
/// Kills any orphaned Instruments processes belonging to the user.
///
/// In some cases, we've seen interactions between Instruments and the iOS
/// simulator that cause hung instruments and DTServiceHub processes. If
/// enough instances pile up, the host machine eventually becomes
/// unresponsive. Until the underlying issue is resolved, manually kill any
/// orphaned instances (where the parent process has died and PPID is 1)
/// before launching another instruments run.
Future<Null> _killOrphanedInstrumentsProcesses() async {
final ProcessResult result = await processManager.run(<String>['ps', '-e', '-o', 'user,ppid,pid,comm']);
if (result.exitCode != 0)
return;
for (String line in result.stdout.split('\n')) {
final Match match = _processRegExp.firstMatch(line.trim());
if (match == null || match[1] != platform.environment['USER'])
continue;
if (<String>['/instruments', '/DTServiceHub'].any(match[3].endsWith)) {
try {
printTrace('Killing orphaned Instruments process: ${match[2]}');
processManager.killPid(int.parse(match[2]));
} catch (_) {
printTrace('Failed to kill orphaned Instruments process:\n$line');
}
}
}
}
Future<String> getAvailableDevices() async { Future<String> getAvailableDevices() async {
await _killOrphanedInstrumentsProcesses();
try { try {
final ProcessResult result = await processManager.run( final ProcessResult result = await processManager.run(
<String>['/usr/bin/instruments', '-s', 'devices']); <String>['/usr/bin/instruments', '-s', 'devices']);
......
...@@ -21,10 +21,10 @@ class MockProcessManager extends Mock implements ProcessManager {} ...@@ -21,10 +21,10 @@ class MockProcessManager extends Mock implements ProcessManager {}
class MockFile extends Mock implements File {} class MockFile extends Mock implements File {}
void main() { void main() {
final FakePlatform osx = new FakePlatform.fromPlatform(const LocalPlatform());
osx.operatingSystem = 'macos';
group('IMobileDevice', () { group('IMobileDevice', () {
final FakePlatform osx = new FakePlatform.fromPlatform(const LocalPlatform());
osx.operatingSystem = 'macos';
group('screenshot', () { group('screenshot', () {
final String outputPath = fs.path.join('some', 'test', 'path', 'image.png'); final String outputPath = fs.path.join('some', 'test', 'path', 'image.png');
MockProcessManager mockProcessManager; MockProcessManager mockProcessManager;
...@@ -68,6 +68,7 @@ void main() { ...@@ -68,6 +68,7 @@ void main() {
group('Xcode', () { group('Xcode', () {
MockProcessManager mockProcessManager; MockProcessManager mockProcessManager;
final FakePlatform fakePlatform = new FakePlatform(environment: <String, String>{'USER': 'rwaters'});
Xcode xcode; Xcode xcode;
setUp(() { setUp(() {
...@@ -213,6 +214,8 @@ void main() { ...@@ -213,6 +214,8 @@ void main() {
}); });
testUsingContext('getAvailableDevices throws ToolExit when instruments is not installed', () async { testUsingContext('getAvailableDevices throws ToolExit when instruments is not installed', () async {
when(mockProcessManager.run(<String>['ps', '-e', '-o', 'user,ppid,pid,comm']))
.thenReturn(new ProcessResult(1, 0, '', ''));
when(mockProcessManager.run(<String>['/usr/bin/instruments', '-s', 'devices'])) when(mockProcessManager.run(<String>['/usr/bin/instruments', '-s', 'devices']))
.thenThrow(const ProcessException('/usr/bin/instruments', const <String>['-s', 'devices'])); .thenThrow(const ProcessException('/usr/bin/instruments', const <String>['-s', 'devices']));
expect(() async => await xcode.getAvailableDevices(), throwsToolExit()); expect(() async => await xcode.getAvailableDevices(), throwsToolExit());
...@@ -221,6 +224,8 @@ void main() { ...@@ -221,6 +224,8 @@ void main() {
}); });
testUsingContext('getAvailableDevices throws ToolExit when instruments returns non-zero', () async { testUsingContext('getAvailableDevices throws ToolExit when instruments returns non-zero', () async {
when(mockProcessManager.run(<String>['ps', '-e', '-o', 'user,ppid,pid,comm']))
.thenReturn(new ProcessResult(1, 0, '', ''));
when(mockProcessManager.run(<String>['/usr/bin/instruments', '-s', 'devices'])) when(mockProcessManager.run(<String>['/usr/bin/instruments', '-s', 'devices']))
.thenReturn(new ProcessResult(1, 1, '', 'Sad today')); .thenReturn(new ProcessResult(1, 1, '', 'Sad today'));
expect(() async => await xcode.getAvailableDevices(), throwsToolExit()); expect(() async => await xcode.getAvailableDevices(), throwsToolExit());
...@@ -229,12 +234,47 @@ void main() { ...@@ -229,12 +234,47 @@ void main() {
}); });
testUsingContext('getAvailableDevices returns instruments output when installed', () async { testUsingContext('getAvailableDevices returns instruments output when installed', () async {
when(mockProcessManager.run(<String>['ps', '-e', '-o', 'user,ppid,pid,comm']))
.thenReturn(new ProcessResult(1, 0, '', ''));
when(mockProcessManager.run(<String>['/usr/bin/instruments', '-s', 'devices'])) when(mockProcessManager.run(<String>['/usr/bin/instruments', '-s', 'devices']))
.thenReturn(new ProcessResult(1, 0, 'Known Devices:\niPhone 6s (10.3.3) [foo]', '')); .thenReturn(new ProcessResult(1, 0, 'Known Devices:\niPhone 6s (10.3.3) [foo]', ''));
expect(await xcode.getAvailableDevices(), 'Known Devices:\niPhone 6s (10.3.3) [foo]'); expect(await xcode.getAvailableDevices(), 'Known Devices:\niPhone 6s (10.3.3) [foo]');
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
ProcessManager: () => mockProcessManager, ProcessManager: () => mockProcessManager,
}); });
testUsingContext('getAvailableDevices works even if orphan listing fails', () async {
when(mockProcessManager.run(<String>['ps', '-e', '-o', 'user,ppid,pid,comm']))
.thenReturn(new ProcessResult(1, 1, '', ''));
when(mockProcessManager.run(<String>['/usr/bin/instruments', '-s', 'devices']))
.thenReturn(new ProcessResult(1, 0, 'Known Devices:\niPhone 6s (10.3.3) [foo]', ''));
expect(await xcode.getAvailableDevices(), 'Known Devices:\niPhone 6s (10.3.3) [foo]');
}, overrides: <Type, Generator>{
ProcessManager: () => mockProcessManager,
});
testUsingContext('getAvailableDevices cleans up orphaned intstruments processes', () async {
when(mockProcessManager.run(<String>['ps', '-e', '-o', 'user,ppid,pid,comm']))
.thenReturn(new ProcessResult(1, 0, '''
USER PPID PID COMM
rwaters 1 36580 /Applications/Xcode.app/Contents/Developer/usr/bin/make
rwaters 36579 36581 /Applications/Xcode.app/Contents/Developer/usr/bin/instruments
rwaters 1 36582 /Applications/Xcode.app/Contents/Developer/usr/bin/instruments
rwaters 1 36583 /Applications/Xcode.app/Contents/SharedFrameworks/DVTInstrumentsFoundation.framework/Resources/DTServiceHub
rwaters 36581 36584 /Applications/Xcode.app/Contents/SharedFrameworks/DVTInstrumentsFoundation.framework/Resources/DTServiceHub
''', ''));
when(mockProcessManager.run(<String>['/usr/bin/instruments', '-s', 'devices']))
.thenReturn(new ProcessResult(1, 0, 'Known Devices:\niPhone 6s (10.3.3) [foo]', ''));
await xcode.getAvailableDevices();
verify(mockProcessManager.killPid(36582));
verify(mockProcessManager.killPid(36583));
verifyNever(mockProcessManager.killPid(36580));
verifyNever(mockProcessManager.killPid(36581));
verifyNever(mockProcessManager.killPid(36584));
}, overrides: <Type, Generator>{
ProcessManager: () => mockProcessManager,
Platform: () => fakePlatform,
});
}); });
group('Diagnose Xcode build failure', () { group('Diagnose Xcode build failure', () {
......
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