diff --git a/src/BuildMonitor.Tests/Launcher/HeadLocatorTests.cs b/src/BuildMonitor.Tests/Launcher/HeadLocatorTests.cs
index 195c5a7..40d79df 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 29e5131..ff33b48 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",