replacePreviousSession function
Stops what the previous session in the current session dir left behind and
removes its files, so a new launch / attach can start cleanly.
A dead session (see isPreviousSessionStale) has its leftover controller,
flutter tool and log collector processes terminated and waited for, and a
single WARNING: line is reported through onProgress. A live session is
replaced: with stopApp its app is first stopped through the controller
(the app.stop path fdb kill uses), then its controller and log
collector receive SIGTERM.
On SIGTERM the controller stops its flutter run / flutter attach child
before exiting (on mobile targets the app keeps running, verified on the
iOS simulator; desktop and web are not verified), so it gets
controllerShutdownTimeout before SIGKILL. fdb launch passes
stopApp because it starts the app again anyway; fdb attach does not,
as it may be attaching to the very app the previous session ran.
Stopping a hung session can take up to about 30 s, so progress lines
prefixed with progressPrefix (launch / attach) are reported through
onProgress before each wait.
Implementation
Future<void> replacePreviousSession({
bool stopApp = false,
String progressPrefix = 'launch',
void Function(String) onProgress = _noop,
}) async {
if (await isPreviousSessionStale()) {
final controllerPid = readControllerPid();
if (controllerPid != null) {
if (isProcessAlive(controllerPid)) onProgress('$progressPrefix: stopping previous session');
await terminateProcess(controllerPid, timeout: controllerShutdownTimeout);
}
final pids = {readPid(), readLogCollectorPid()}.whereType<int>().where((pid) => pid != controllerPid);
for (final pid in pids) {
await terminateProcess(pid, timeout: const Duration(seconds: 3));
}
cleanupLaunchSessionFiles();
onProgress('WARNING: Cleaned up a stale session (previous app or controller was no longer running)');
return;
}
onProgress('$progressPrefix: stopping previous session');
if (stopApp) {
try {
await sendControllerCommand(ControllerCommand.kill, timeout: const Duration(seconds: killTimeoutSeconds));
} catch (_) {
// Best effort: the controller is signalled below either way.
}
}
final logCollectorPid = readLogCollectorPid();
if (logCollectorPid != null && isProcessAlive(logCollectorPid)) {
try {
Process.killPid(logCollectorPid, ProcessSignal.sigterm);
} catch (_) {}
}
final controllerPid = readControllerPid();
if (controllerPid != null && isProcessAlive(controllerPid)) {
onProgress('$progressPrefix: waiting for the previous session to exit');
await terminateProcess(controllerPid, timeout: controllerShutdownTimeout);
}
cleanupLaunchSessionFiles();
}