From 3327749fbbe512cd78f99d13a9cd63b6560ba229 Mon Sep 17 00:00:00 2001 From: Rui Marinho Date: Wed, 18 Mar 2026 18:37:49 +0000 Subject: [PATCH] Fix EmulatorRunner early exit detection and macOS fork handling Add early process exit detection in BootEmulatorAsync boot polling loop. Previously, if the emulator failed immediately (e.g., insufficient disk space, missing AVD), the full 300s timeout was wasted before reporting. On macOS, the emulator binary forks the real QEMU process and the parent exits with code 0 immediately. Only non-zero exit codes are treated as immediate failures; exit code 0 continues polling since the real emulator runs as a separate process. Context: dotnet/android#10965 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Runners/EmulatorRunner.cs | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/Xamarin.Android.Tools.AndroidSdk/Runners/EmulatorRunner.cs b/src/Xamarin.Android.Tools.AndroidSdk/Runners/EmulatorRunner.cs index d2ac7f58..4a4bd6e8 100644 --- a/src/Xamarin.Android.Tools.AndroidSdk/Runners/EmulatorRunner.cs +++ b/src/Xamarin.Android.Tools.AndroidSdk/Runners/EmulatorRunner.cs @@ -204,10 +204,33 @@ public async Task BootEmulatorAsync ( // Poll for the new emulator serial to appear. // If the boot times out or is cancelled, terminate the process we spawned // to avoid leaving orphan emulator processes. + // + // On macOS, the emulator binary may fork the real QEMU process and exit with + // code 0 immediately. The real emulator continues as a separate process and + // will eventually appear in 'adb devices'. We only treat non-zero exit codes + // as immediate failures; exit code 0 means we continue polling. try { string? newSerial = null; + bool processExitedWithZero = false; while (newSerial == null) { timeoutCts.Token.ThrowIfCancellationRequested (); + + // Detect early process exit for fast failure + if (emulatorProcess.HasExited && !processExitedWithZero) { + if (emulatorProcess.ExitCode != 0) { + emulatorProcess.Dispose (); + return new EmulatorBootResult { + Success = false, + ErrorKind = EmulatorBootErrorKind.LaunchFailed, + ErrorMessage = $"Emulator process for '{deviceOrAvdName}' exited with code {emulatorProcess.ExitCode} before becoming available.", + }; + } + // Exit code 0: emulator likely forked (common on macOS). + // The real emulator runs as a separate process — keep polling. + logger.Invoke (TraceLevel.Verbose, $"Emulator launcher process exited with code 0 (likely forked). Continuing to poll adb devices."); + processExitedWithZero = true; + } + await Task.Delay (options.PollInterval, timeoutCts.Token).ConfigureAwait (false); devices = await adbRunner.ListDevicesAsync (timeoutCts.Token).ConfigureAwait (false);