Merge nucleic/olive-jade-civet-rznt into dev

This commit is contained in:
2026-07-29 01:06:05 -07:00
parent 0b0c8fe781
commit 88175578fc
2 changed files with 96 additions and 33 deletions
+18 -11
View File
@@ -33,7 +33,8 @@ Two facts it discovered that anything referencing this package needs:
cd windows/spikes/WslcApiDump
dotnet run # dump the API + check every facade assumption
dotnet run -- --probe # + GetMissingComponents / GetVersion
dotnet run -- --session # + create a session, then a SECOND one with the same name
dotnet run -- --session # + create a session, a SECOND with the same name, identity-test, tear down
dotnet run -- --session --keep # …and leave the sessions running afterwards
dotnet run -- --all-types # include the ABI/marshalling plumbing in the dump
```
@@ -86,17 +87,23 @@ name**. That second construction is the whole point, and it is the last open que
on: the compat SDK exposes a `Session` *constructor* and no attach, so does constructing over an
existing name re-adopt it, or refuse?
- **Refused** (expect `WSLC_E_SESSION_RESERVED`, `0x80040607`) — D13's premise is confirmed on
hardware rather than inferred from an IDL, and session reattach genuinely requires
`IWSLCSessionManager::OpenSessionByName` on the internal interface.
- **Constructed** — the compat surface may re-adopt by name, and the session half of §2.3 reattach
may not need the internal interface at all. Check `wslc container ps` for the first session's
containers before believing it.
- **Refused** (`WSLC_E_SESSION_RESERVED`, `0x80040607`) — D13's premise is confirmed, and session
reattach genuinely requires `IWSLCSessionManager::OpenSessionByName` on the internal interface.
- **Constructed** — which is what 2.9.4 actually does. That alone proves nothing (construction may
be lazy), so the probe then runs an **identity test**: terminate the FIRST session and read from
the SECOND. A read that worked before and fails after is one underlying session answering both
handles; a read that keeps working means two independent VMs were running.
It leaves the session running on purpose, because that is the other half of the same question:
broker supervision assumes wslc state is **service-backed**, so a crashed `nucleic-brokerd` can
re-adopt rather than orphaning containers. If `wslc session ls` still shows `nucleic-spike` after
this process exits, that holds. Tear it down with `wslc` when you're done.
The identity test deliberately uses only the compat SDK. The obvious check would be
`wslc session ls` — but **`wslc.exe` is not on PATH by default**, so a spike that depends on it
answers nothing on a stock machine. (It ships beside `wsl.exe`; try
`C:\Program Files\WSL\wslc.exe`. That it isn't on PATH is one more small argument for D13's
no-CLI stance.)
Sessions are **torn down at the end** by default — an earlier version left a WSL VM running and
told you to clean it up with a command that doesn't exist. Pass `--keep` to leave them, which is
how you check the other half of the §2.3 question: whether session state outlives the process that
created it. With `--keep`, `wsl --shutdown` clears everything.
The gateway address is *not* what this probe is for any more — §13.1 established that no API
surfaces one, and D13 moves the control plane to hvsocket via `IWSLCVirtualMachine::GetId`.
+77 -21
View File
@@ -89,7 +89,9 @@ internal static class Program
}
else
{
ProbeSession(types, ArgValue(args, "--session-name") ?? "nucleic-spike");
ProbeSession(
types, ArgValue(args, "--session-name") ?? "nucleic-spike",
keep: args.Contains("--keep"));
}
}
@@ -355,7 +357,7 @@ internal static class Program
///
/// Leaves the session running on purpose: whether it outlives this process is the other half
/// of the same question. Tear it down with `wslc` when you are done.
private static void ProbeSession(Type[] types, string name)
private static void ProbeSession(Type[] types, string name, bool keep)
{
Console.WriteLine();
Console.WriteLine($"live probe (creates session '{name}'):");
@@ -411,29 +413,83 @@ internal static class Program
}
}
Console.WriteLine(" → CONSTRUCTED — the constructor did NOT refuse.");
IdentityTest(sessionType, first, second);
}
Console.WriteLine();
// Be honest about what this does and does not show. A constructed object is not proof
// of attachment: construction may simply be lazy, and WSLC_E_SESSION_RESERVED clearly
// fires under SOME condition or it would not exist in wslc.idl. The compat surface has
// no enumeration, so identity cannot be settled from inside this process.
Console.WriteLine(" This does NOT by itself prove it re-adopted the first session —");
Console.WriteLine(" construction may be lazy, and WSLC_E_SESSION_RESERVED exists in");
Console.WriteLine(" wslc.idl, so it fires under some condition. The compat surface has");
Console.WriteLine(" no enumeration, so identity cannot be settled from in here.");
Console.WriteLine();
Console.WriteLine(" DECISIVE CHECK — run this now: wslc session ls");
Console.WriteLine($" ONE session named '{name}' → it attached; the SESSION half of");
Console.WriteLine(" §2.3 reattach may not need the");
Console.WriteLine(" internal COM interface after all.");
Console.WriteLine($" TWO sessions named '{name}' → it did not; two VMs are running and");
Console.WriteLine(" the name is not an identity.");
Console.WriteLine(" Either way the CONTAINER half of D13 is unaffected: there is still");
Console.WriteLine(" no way to enumerate containers on the compat surface.");
if (keep)
{
Console.WriteLine(" --keep: session(s) left running, so you can check whether they "
+ "outlive this process (the other half of the §2.3 question).");
Console.WriteLine(" Tear down with `wsl --shutdown` — note that `wslc.exe` is NOT on "
+ "PATH by default; it lives beside wsl.exe (try `C:\\Program Files\\WSL\\wslc.exe`).");
}
else
{
Terminate(sessionType, first, "first");
Console.WriteLine(" (pass --keep to leave sessions running instead)");
}
}
/// Decide whether the second same-named `Session` re-adopted the first, using ONLY the compat
/// SDK — no CLI, which matters because `wslc.exe` is not on PATH by default.
///
/// The trick: terminate the FIRST session and see whether the SECOND dies with it. A read that
/// worked before termination and fails after is the same underlying session answering; a read
/// that keeps working means two independent sessions were running all along. `GetImages` is
/// the cheapest read on the surface and mutates nothing.
private static void IdentityTest(Type sessionType, object first, object second)
{
var getImages = sessionType.GetMethods(Public)
.FirstOrDefault(m => !m.IsStatic && m.Name == "GetImages" && m.GetParameters().Length == 0);
if (getImages is null) { Console.WriteLine(" (no GetImages — cannot test identity)"); return; }
Console.WriteLine();
Console.WriteLine(" session left running — `wslc session ls`; whether it outlives this "
+ "process is the other half of the §2.3 reattach question.");
Console.WriteLine(" identity test: terminate the FIRST, then read from the SECOND");
if (!Reads(getImages, second, out var before))
{
Console.WriteLine($" inconclusive — the second session already fails to read: {before}");
return;
}
Console.WriteLine(" second reads ok before termination");
if (!Terminate(sessionType, first, "first")) return;
// The service tears a VM down asynchronously; give it a moment before concluding.
Thread.Sleep(3000);
if (Reads(getImages, second, out var after))
{
Console.WriteLine(" second STILL reads ok after the first was terminated");
Console.WriteLine(" → SEPARATE SESSIONS. The name is not an identity: two VMs were "
+ "running. D13 stands — reattach needs IWSLCSessionManager::OpenSessionByName.");
}
else
{
Console.WriteLine($" second now fails: {after}");
Console.WriteLine(" → SAME SESSION. Constructing over an existing name RE-ADOPTS it, "
+ "so the session half of §2.3 reattach is reachable from the compat surface and "
+ "D13's reattach row softens (the container half is unaffected — still no "
+ "enumeration).");
}
}
private static bool Reads(MethodInfo getImages, object session, out string detail)
{
try { getImages.Invoke(session, null); detail = "ok"; return true; }
catch (TargetInvocationException e) { detail = Describe(e.InnerException); return false; }
catch (Exception e) { detail = Describe(e); return false; }
}
private static bool Terminate(Type sessionType, object session, string which)
{
var terminate = sessionType.GetMethods(Public)
.FirstOrDefault(m => !m.IsStatic && m.Name == "Terminate" && m.GetParameters().Length == 0);
if (terminate is null) { Console.WriteLine(" (no Session.Terminate)"); return false; }
try { terminate.Invoke(session, null); Console.WriteLine($" {which} session terminated"); return true; }
catch (TargetInvocationException e)
{
Console.WriteLine($" {which} Terminate() threw {Describe(e.InnerException)}");
return false;
}
}
private static object? CreateSession(