runSupervised function
Runs the worker in supervised mode.
Launches the same binary as a child process (without --supervised) and monitors its exit code:
- exit 0: normal shutdown, supervisor exits too.
- exit 42: update requested, restart the worker.
- other: crash, wait 10 seconds and restart.
With pub.dev-based updates, dart pub global activate is already
executed by the auto_updater before exiting with code 42.
The supervisor only needs to restart the process.
Implementation
Future<void> runSupervised(List<String> arguments) async {
final workerArgs = arguments.where((a) => a != '--supervised').toList();
// Detect if running via pub global (Platform.resolvedExecutable points to
// the dart binary, not a compiled AOT executable).
final resolvedExe = Platform.resolvedExecutable;
final isRunningViaPubGlobal =
resolvedExe.endsWith('/dart') || resolvedExe.endsWith(r'\dart.exe');
while (true) {
_log.info('Starting worker process...');
final String executable;
final List<String> processArgs;
final installedAot = _findInstalledAotBinary();
if (installedAot != null) {
_log.info('Found installed AOT binary: $installedAot');
executable = installedAot;
processArgs = workerArgs;
} else if (isRunningViaPubGlobal) {
// Use `dart pub global run` to re-enter through the package entrypoint.
executable = resolvedExe;
processArgs = [
'pub',
'global',
'run',
'openci_worker_cli',
...workerArgs,
];
} else {
// AOT-compiled binary: just re-run self.
executable = resolvedExe;
processArgs = workerArgs;
}
Process process;
StreamSubscription? sigtermSub;
StreamSubscription? sigintSub;
try {
process = await Process.start(
executable,
processArgs,
mode: ProcessStartMode.inheritStdio,
);
// Forward system signals (SIGTERM / SIGINT) to child process to ensure graceful shutdown
// and prevent child process from becoming a zombie when supervisor is terminated.
if (!Platform.isWindows) {
sigtermSub = ProcessSignal.sigterm.watch().listen((sig) {
_log.info(
'Supervisor received SIGTERM. Forwarding to child process...',
);
process.kill(ProcessSignal.sigterm);
});
sigintSub = ProcessSignal.sigint.watch().listen((sig) {
_log.info(
'Supervisor received SIGINT. Forwarding to child process...',
);
process.kill(ProcessSignal.sigint);
});
}
} on ProcessException catch (e, s) {
_log.warning(
'Failed to start worker process (binary may be temporarily missing or locked): $e. '
'Restarting in 10 seconds...',
);
await Sentry.captureException(e, stackTrace: s);
await Future<void>.delayed(const Duration(seconds: 10));
continue;
}
final exitCode = await process.exitCode;
await sigtermSub?.cancel();
await sigintSub?.cancel();
switch (exitCode) {
case 0:
_log.info('Worker exited normally.');
return;
case exitCodeUpdateRequested:
_log.info('Update installed. Restarting worker...');
default:
_log.warning(
'Worker crashed (exit code $exitCode). Restarting in 10 seconds...',
);
await Future<void>.delayed(const Duration(seconds: 10));
}
}
}