镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content

[AndroidSdk] Limit inherited handles for persistent emulators - #13523

Closed
simonrozsival wants to merge 2 commits into
simonrozsival-license-installer-process-pumpsfrom
simonrozsival-persistent-emulator-lifetimes
Closed

simonrozsival wants to merge 2 commits into
simonrozsival-license-installer-process-pumpsfrom
simonrozsival-persistent-emulator-lifetimes

Conversation

@simonrozsival

@simonrozsival simonrozsival commented Nov 12, 2026 •

Copy link
Copy Markdown
Member

Context: #12981
Context: https://devblogs.microsoft.com/dotnet/process-api-improvements-in-dotnet-11/

EmulatorRunner.LaunchEmulator() and the bootstrap <StartAndroidEmulator/> task start emulator processes that intentionally outlive the code that launched them. Until now they used the default ProcessStartInfo handle inheritance, so a long-lived emulator received every inheritable handle the parent had open at that moment, not just its own redirected stdout/stderr.

That matters because pipe EOF is only observed once every copy of the write end is closed. If another process was being started concurrently with redirected output (for example adb or emulator -list-avds), the emulator could inherit that sibling's pipe handle and keep it open for the emulator's whole lifetime. The sibling's reader would then never see EOF and would hang or run into its timeout.

.NET 11 adds ProcessStartInfo.InheritedHandles. An empty list means only the standard handles are inherited, so set InheritedHandles = [] on both emulator launches. Xamarin.Android.Tools.AndroidSdk also targets netstandard2.0, where the property does not exist, so the EmulatorRunner assignment is guarded by #if NET11_0_OR_GREATER; netstandard2.0 behavior is unchanged.

Everything else stays as it was:

  • stdout/stderr are still redirected and forwarded to the logger (LaunchEmulator) or scanned for startup errors (<StartAndroidEmulator/>);
  • the Unix /bin/sh -c "trap '' INT; exec …" launch and argument quoting are unchanged, so the emulator still survives Ctrl+C in the parent terminal;
  • StartDetached is deliberately not used, because it would replace the standard handles with the null device and discard emulator diagnostics.

This PR replaces an earlier, much larger revision (a new EmulatorProcess type, file-backed logs, process groups and Windows job objects) with this minimal, targeted change.

Note that Xamarin.Android.Build.Tasks targets netstandard2.0, so the in-product <BootAndroidEmulator/> → EmulatorRunner.BootEmulatorAsync() path compiles against the netstandard2.0 build where the guarded assignment is excluded; only net11.0+ consumers of Xamarin.Android.Tools.AndroidSdk and the bootstrap <StartAndroidEmulator/> task get the new behavior.

Testing (macOS, DOTNET_ROOT = locally built .NET runtime 12.0.0-alpha.1.26556.115):

  • netstandard2.0: dotnet build src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj -nr:false -p:NuGetAudit=false -p:AndroidToolsDisableMultiTargeting=false -p:DotNetTargetFrameworkVersion=11.0 -f netstandard2.0 succeeded, 0 warnings, 0 errors.

  • net11.0: the same command with -f net11.0 succeeded, 0 warnings, 0 errors.

  • Default net12.0 (dotnet build src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj -nr:false -p:NuGetAudit=false -p:AndroidToolsDisableMultiTargeting=false, resolving TargetFrameworks=netstandard2.0;net12.0) fails with exit code 1 on both this PR and its base 07c58d96, with the same two errors:

    CSC : error CS2001: Source file 'src/Xamarin.Android.Tools.AndroidSdk/PublicAPI/net12.0/PublicAPI.Shipped.txt' could not be found.
    CSC : error CS2001: Source file 'src/Xamarin.Android.Tools.AndroidSdk/PublicAPI/net12.0/PublicAPI.Unshipped.txt' could not be found.
    

    The project adds PublicAPI/$(TargetFramework)/* unconditionally, and only net10.0, net11.0 and netstandard2.0 baselines exist; there is no supported property to select or generate them, so net12.0 compilation was not validated.

  • Bootstrap (net11.0): dotnet build build-tools/Xamarin.Android.Tools.BootstrapTasks/Xamarin.Android.Tools.BootstrapTasks.csproj succeeded.

  • dotnet test tests/Xamarin.Android.Tools.AndroidSdk-Tests/Xamarin.Android.Tools.AndroidSdk-Tests.csproj -nr:false -p:NuGetAudit=false --filter FullyQualifiedName~EmulatorRunnerTests passed 27 of 27 (test assembly net11.0, against the net11.0 Xamarin.Android.Tools.AndroidSdk build), at head 2dcfcd169b.

  • No new test asserts the non-inheritance itself. Windows, Linux and on-device runs were not done.

Copilot AI balanced review requested due to automatic review settings November 12, 2026 12:51

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Cleanup can kill an unrelated process group if the cached launcher PID is reused, and a throwing logger can cause disposal to terminate a successfully booted emulator.

3 open issues
What changed in this PR

This PR separates persistent emulator lifetimes from caller-owned streams in Android SDK tooling and bootstrap tasks, addressing part of #12981.

Changes:

  • Introduces shared emulator lifetime handling with retained diagnostic files, null stdin, and platform-specific cleanup.
  • Updates boot and bootstrap startup to join diagnostic forwarding, handle cancellation, and preserve successful emulators.
  • Adds Unix lifecycle regression tests and documents the ownership contract.
File Description
tests/​Xamarin.Android.Tools.AndroidSdk-Tests/​EmulatorRunnerTests.cs Adds persistence, cancellation, fork cleanup, and diagnostic tests.
tests/​Microsoft.Android.Build.BaseTasks-Tests/​StartAndroidEmulatorTests.cs Adds bootstrap startup and lifetime regression tests.
tests/​Microsoft.Android.Build.BaseTasks-Tests/​Microsoft.Android.Build.BaseTasks-Tests.csproj References bootstrap tasks for testing.
src/​Xamarin.Android.Tools.AndroidSdk/​Runners/​EmulatorRunner.cs Integrates lifetime ownership and bounds discovery within the boot deadline.
src/​Xamarin.Android.Tools.AndroidSdk/​Runners/​EmulatorProcess.cs Implements independent streams, diagnostic forwarding, and owned-process cleanup.
src/​Xamarin.Android.Tools.AndroidSdk/​README.md Documents persistence, disposal, and cleanup behavior.
build-tools/​Xamarin.Android.Tools.BootstrapTasks/​Xamarin.Android.Tools.BootstrapTasks/​StartAndroidEmulator.cs Adopts shared lifetime handling and adds cancellation support.
build-tools/​Xamarin.Android.Tools.BootstrapTasks/​Xamarin.Android.Tools.BootstrapTasks.csproj Links the shared emulator implementation.
.github/​skills/​tests/​references/​test-catalog.md Adds the focused bootstrap emulator test command.

Review effort: Balanced

} else {
// StartDetached creates a new Unix session and process group. A forked
// QEMU keeps this group even after the launcher has exited/reparented.
if (KillProcessGroup (-processId, 9) != 0 && Marshal.GetLastPInvokeError () != 3)
Comment on lines +84 to +87
if (!AssignProcessToJobObject (job, process.SafeHandle))
throw new Win32Exception (Marshal.GetLastPInvokeError ());
process.assignedToJob = true;
process.SafeHandle.Resume ();
Comment on lines +178 to +183
try {
CompleteOutput ();
} catch {
Stop ();
throw;
}
@simonrozsival
simonrozsival force-pushed the simonrozsival-license-installer-process-pumps branch from d4f9f1c to a235b37 Compare November 12, 2026 16:48
@simonrozsival
simonrozsival force-pushed the simonrozsival-persistent-emulator-lifetimes branch 2 times, most recently from af3d0b8 to 3b25c37 Compare November 13, 2026 09:24
@simonrozsival simonrozsival changed the title [AndroidSdk] Preserve persistent emulator lifetimes [AndroidSdk] Limit inherited handles for persistent emulators Nov 13, 2026
@simonrozsival
simonrozsival removed this pull request from stack #13517 November 13, 2026 09:57
@simonrozsival
simonrozsival force-pushed the simonrozsival-license-installer-process-pumps branch 2 times, most recently from 7d5da70 to c545363 Compare November 13, 2026 10:51
simonrozsival and others added 2 commits November 13, 2026 12:07
Set the .NET 11 `ProcessStartInfo.InheritedHandles = []` for the
long-lived emulator processes started by `EmulatorRunner.LaunchEmulator`
and the bootstrap `StartAndroidEmulator` task, so the emulator only
inherits its standard handles instead of every inheritable handle in
the parent (e.g. other concurrently started processes' pipes).

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
`ProcessStartInfo.InheritedHandles` is .NET 11+ API, and
Xamarin.Android.Tools.AndroidSdk also targets `netstandard2.0`, so
only set it under `NET11_0_OR_GREATER`; netstandard2.0 behavior is
unchanged.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@simonrozsival
simonrozsival force-pushed the simonrozsival-persistent-emulator-lifetimes branch from 3b25c37 to 2dcfcd1 Compare November 13, 2026 11:08
@simonrozsival

Copy link
Copy Markdown
Member Author

Closing: per discussion, the emulator InheritedHandles change isn't worth carrying (the shipped boot path compiles against netstandard2.0, where it's excluded).

@simonrozsival
simonrozsival removed this pull request from stack #13545 November 13, 2026 11:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants