From 9d92d5b541f3f4e539afb2373e6803d16cedd5ca Mon Sep 17 00:00:00 2001 From: Simon Cropp Date: Sun, 11 Oct 2026 09:01:42 +1100 Subject: [PATCH] Probe a musl RID and nothing else for the head HeadLocator tried the framework's own RID and then linux-{arch}, which is the glibc head, so on Alpine the probe that was meant to miss found an apphost that cannot start. The launcher started it, waited 15 seconds and reported a tray that did not answer, rather than that there is no head for linux-musl-x64. A musl RID now yields itself alone, as NativeResolver and DiffEngine's resolver do. --- .../Launcher/HeadLocatorTests.cs | 25 +++++++++++++++++ src/BuildMonitor/HeadLocator.cs | 27 ++++++++++++++++--- 2 files changed, 48 insertions(+), 4 deletions(-) diff --git a/src/BuildMonitor.Tests/Launcher/HeadLocatorTests.cs b/src/BuildMonitor.Tests/Launcher/HeadLocatorTests.cs index 195c5a7a..40d79df3 100644 --- a/src/BuildMonitor.Tests/Launcher/HeadLocatorTests.cs +++ b/src/BuildMonitor.Tests/Launcher/HeadLocatorTests.cs @@ -35,4 +35,29 @@ public async Task RidsStartWithTheExactOne() await Assert.That(rids[0]).IsEqualTo(RuntimeInformation.RuntimeIdentifier); await Assert.That(rids.Count).IsGreaterThanOrEqualTo(1); } + + /// + /// The probe for a musl RID is that RID alone. The linux-{arch} candidate after it names the + /// glibc head, which cannot start on musl: the launcher would wait out its timeout for a tray + /// that was never going to answer, rather than say there is no head for the machine. + /// + [Test] + [Arguments("linux-musl-x64")] + [Arguments("linux-musl-arm64")] + public async Task MuslProbesItsOwnRidAndNothingElse(string runtimeIdentifier) + { + var rids = HeadLocator.Rids(runtimeIdentifier).ToList(); + await Assert.That(rids).IsEquivalentTo([runtimeIdentifier]); + } + + /// + /// Everywhere else the synthesised RID still follows the framework's own. + /// + [Test] + public async Task OtherRidsStillFallBackToTheSynthesisedRid() + { + var rids = HeadLocator.Rids("some-rid").ToList(); + await Assert.That(rids.Count).IsGreaterThan(1); + await Assert.That(rids[0]).IsEqualTo("some-rid"); + } } diff --git a/src/BuildMonitor/HeadLocator.cs b/src/BuildMonitor/HeadLocator.cs index 29e51316..ff33b48a 100644 --- a/src/BuildMonitor/HeadLocator.cs +++ b/src/BuildMonitor/HeadLocator.cs @@ -1,7 +1,11 @@ /// /// Finds the tray executable for this machine under heads/{rid}. The exact RID is tried first, -/// then the plain os-arch one, the same order DiffEngine resolves its native renderers in, so -/// a linux-musl-x64 machine misses rather than starting a glibc build. +/// then the plain os-arch one, the same order DiffEngine resolves its native renderers in. +/// +/// A musl RID is the exception and is tried alone. No head is built for musl, and the os-arch +/// one after it is linux-{arch}, the glibc build: found, it would be started and never answer, +/// so a linux-musl-x64 machine has to miss here to be told there is no head for it. +/// /// static class HeadLocator { @@ -31,9 +35,24 @@ static class HeadLocator return null; } - public static IEnumerable Rids() + public static IEnumerable Rids() => + Rids(RuntimeInformation.RuntimeIdentifier); + + public static IEnumerable Rids(string runtimeIdentifier) { - yield return RuntimeInformation.RuntimeIdentifier; + // The exact RID first. On Alpine that is linux-musl-x64, which we do not ship, so the + // probe misses and the launcher says there is no head for this machine. + yield return runtimeIdentifier; + + // And nothing else, or the synthesised RID below undoes that: linux-{arch} is the glibc + // build, an apphost that cannot start on musl. The launcher would start it detached, wait + // out its timeout and report a tray that did not answer, when the truth is that no head + // is built for this machine. + if (runtimeIdentifier.Contains("-musl-", StringComparison.Ordinal)) + { + yield break; + } + var architecture = RuntimeInformation.OSArchitecture switch { Architecture.X64 => "x64",