Skip to content

Commit 354589b

Browse files
committed
Kill all the child processes
1 parent 0905a5d commit 354589b

File tree

2 files changed

+54
-10
lines changed

2 files changed

+54
-10
lines changed

pkgs/dart_mcp_server/lib/src/mixins/flutter_launcher.dart

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ base mixin FlutterLauncherSupport
227227
} catch (e, s) {
228228
log(LoggingLevel.error, 'Error launching Flutter application: $e\n$s');
229229
if (process != null) {
230-
process.kill();
230+
processManager.killPid(process.pid);
231231
// The exitCode handler will perform the rest of the cleanup.
232232
}
233233
return CallToolResult(
@@ -243,19 +243,19 @@ base mixin FlutterLauncherSupport
243243
final stopAppTool = Tool(
244244
name: 'stop_app',
245245
description:
246-
'Kills a running Flutter process started by the launch_app tool.',
246+
'Stops a running Flutter process started by the launch_app tool.',
247247
inputSchema: Schema.object(
248248
properties: {
249249
'pid': Schema.int(
250-
description: 'The process ID of the process to kill.',
250+
description: 'The process ID of the process to stop.',
251251
),
252252
},
253253
required: ['pid'],
254254
),
255255
outputSchema: Schema.object(
256256
properties: {
257257
'success': Schema.bool(
258-
description: 'Whether the process was killed successfully.',
258+
description: 'Whether the process was stopped successfully.',
259259
),
260260
},
261261
required: ['success'],
@@ -274,8 +274,33 @@ base mixin FlutterLauncherSupport
274274
content: [TextContent(text: 'Application with PID $pid not found.')],
275275
);
276276
}
277-
278-
final success = processManager.killPid(pid);
277+
// On Unix, killing the flutter process doesn't kill the entire process
278+
// group, so we have to look for the child processes.
279+
if (Platform.isLinux) {
280+
final ps = processManager.runSync([
281+
'ps',
282+
'--no-headers',
283+
'--format',
284+
'%p',
285+
'--ppid',
286+
'$pid',
287+
]);
288+
if (ps.exitCode == 0) {
289+
final children = (ps.stdout as String).trim().split('\n');
290+
if (children.isNotEmpty) {
291+
for (final child in children) {
292+
int childPid;
293+
try {
294+
childPid = int.parse(child);
295+
} on FormatException {
296+
continue;
297+
}
298+
processManager.killPid(childPid, ProcessSignal.sigterm);
299+
}
300+
}
301+
}
302+
}
303+
final success = processManager.killPid(app.process.pid);
279304
if (success) {
280305
log(
281306
LoggingLevel.info,

pkgs/dart_mcp_server/test/tools/flutter_launcher_test.dart

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,7 @@ void main() {
460460
'TimeoutException',
461461
]),
462462
);
463+
test.expect(mockProcessManager.killedPids, [processPid]);
463464

464465
server.shutdown();
465466
client.shutdown();
@@ -504,6 +505,15 @@ void main() {
504505
pid: processPid,
505506
),
506507
);
508+
if (Platform.isLinux) {
509+
mockProcessManager.addCommand(
510+
Command(
511+
['ps', '--no-headers', '--format', '%p', '--ppid', '$processPid'],
512+
stdout: '11111\n22222\n',
513+
pid: processPid,
514+
),
515+
);
516+
}
507517
final serverAndClient = await createServerAndClient(
508518
processManager: mockProcessManager,
509519
fileSystem: fileSystem,
@@ -532,9 +542,9 @@ void main() {
532542
CallToolRequest(name: 'stop_app', arguments: {'pid': processPid}),
533543
);
534544

535-
test.expect(result.isError, test.isNot(true));
536545
test.expect(result.structuredContent, {'success': true});
537-
test.expect(mockProcessManager.killedPids, [processPid]);
546+
test.expect(mockProcessManager.killedPids, [11111, 22222, processPid]);
547+
test.expect(result.isError, test.isNot(true));
538548
await server.shutdown();
539549
await client.shutdown();
540550
});
@@ -719,7 +729,9 @@ class MockProcessManager implements ProcessManager {
719729
}
720730
}
721731
throw Exception(
722-
'Command not mocked: $command. Mocked commands:\n${_commands.join('\n')}',
732+
'Command not mocked: "${command.join(' ')}".\n'
733+
'Mocked commands:\n'
734+
'${_commands.map<String>((e) => e.command.join(' ')).join('\n')}',
723735
);
724736
}
725737

@@ -788,7 +800,14 @@ class MockProcessManager implements ProcessManager {
788800
Encoding? stdoutEncoding = systemEncoding,
789801
Encoding? stderrEncoding = systemEncoding,
790802
}) {
791-
throw UnimplementedError();
803+
commands.add(command);
804+
final mockCommand = _findCommand(command);
805+
return ProcessResult(
806+
mockCommand.pid,
807+
0,
808+
mockCommand.stdout ?? '',
809+
mockCommand.stderr ?? '',
810+
);
792811
}
793812
}
794813

0 commit comments

Comments
 (0)