From 9f125628a0a52011d11b59fb05c9b7c0bf759ee5 Mon Sep 17 00:00:00 2001 From: Nucleic Date: Wed, 29 Jul 2026 01:10:27 -0700 Subject: [PATCH] Merge nucleic/olive-jade-civet-rznt into dev --- spikes/README.md | 16 ++++++---- spikes/WslcApiDump/Program.cs | 55 +++++++++++++++++++++++++---------- 2 files changed, 50 insertions(+), 21 deletions(-) diff --git a/spikes/README.md b/spikes/README.md index 1e08d89..ea485eb 100644 --- a/spikes/README.md +++ b/spikes/README.md @@ -87,12 +87,16 @@ 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** (`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. +**Answered on 2.9.4: it cannot.** The constructor is lazy and always succeeds — judging by it is +what made the first reading of this probe wrong. `Start()` is where the service is consulted, and +a second `Start()` on a running name fails with `ERROR_ALREADY_EXISTS` (0x800700B7). Note it is +*not* `WSLC_E_SESSION_RESERVED`, which exists in `wslc.idl` but evidently means something +narrower — don't key on it. + +So the probe judges on `Start()`. If a future version lets the second `Start()` through, it 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. 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 diff --git a/spikes/WslcApiDump/Program.cs b/spikes/WslcApiDump/Program.cs index ad69bd7..8e8af93 100644 --- a/spikes/WslcApiDump/Program.cs +++ b/spikes/WslcApiDump/Program.cs @@ -15,10 +15,11 @@ namespace WslcApiDump; /// /// It never mutates anything unless asked: the default run only reads type metadata. `--probe` /// additionally calls the two safe statics (`GetVersion`, `GetMissingComponents`). `--session` -/// goes further and creates a real session, then creates a SECOND one with the same name — which -/// is the open question D13 turns on: whether the compat SDK's constructor can re-adopt a session -/// by name, or refuses with `WSLC_E_SESSION_RESERVED` and leaves reattach to the internal COM -/// interface (docs/WINDOWS_PORT.md §13.1). +/// goes further and creates a real session, then a SECOND one with the same name, to settle +/// whether the compat SDK can re-adopt a running session. Answered on 2.9.4: it cannot. The +/// constructor is lazy and always succeeds; `Start()` is where the service is consulted, and it +/// refuses with `ERROR_ALREADY_EXISTS`. So reattach needs the internal COM interface, which is +/// what D13 assumes (docs/WINDOWS_PORT.md §13.1). /// internal static class Program { @@ -402,18 +403,38 @@ internal static class Program } else { - // Starting it is one more signal: re-adopting a RUNNING session should be a no-op, - // whereas a genuinely separate session would be booting a second VM here. - if (start is not null) + // The constructor is LAZY — it only captures settings, so its success says nothing. + // `Start()` is where the service is actually asked for the session, and therefore + // where exclusivity is enforced. Judge on that. + var secondStarted = false; + string? startFailure = null; + if (start is null) { - try { start.Invoke(second, null); Console.WriteLine(" second Start() ok"); } - catch (TargetInvocationException e) - { - Console.WriteLine($" second Start() threw {Describe(e.InnerException)}"); - } + Console.WriteLine(" (no Session.Start() — cannot judge)"); + } + else + { + try { start.Invoke(second, null); secondStarted = true; } + catch (TargetInvocationException e) { startFailure = Describe(e.InnerException); } + } + + if (secondStarted) + { + Console.WriteLine(" second Start() ok — it did NOT refuse, so it may have re-adopted"); + IdentityTest(sessionType, first, second); + } + else if (startFailure is not null) + { + Console.WriteLine($" second Start() threw {startFailure}"); + Console.WriteLine(); + Console.WriteLine(" → REFUSED at Start(). The constructor succeeding meant nothing:"); + Console.WriteLine(" it is lazy, and the service is only consulted by Start()."); + Console.WriteLine(" So the compat SDK CANNOT re-adopt a running session, and D13"); + Console.WriteLine(" holds — §2.3 reattach needs IWSLCSessionManager::OpenSessionByName"); + Console.WriteLine(" (or EnterSession) on the internal COM interface."); + Console.WriteLine(" Note the code is ERROR_ALREADY_EXISTS, not WSLC_E_SESSION_RESERVED;"); + Console.WriteLine(" the reserved code evidently means something narrower."); } - Console.WriteLine(" → CONSTRUCTED — the constructor did NOT refuse."); - IdentityTest(sessionType, first, second); } Console.WriteLine(); if (keep) @@ -447,7 +468,8 @@ internal static class Program 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}"); + // Only reachable if Start() reported success but the session is unusable anyway. + Console.WriteLine($" inconclusive — the second session reads fail already: {before}"); return; } Console.WriteLine(" second reads ok before termination"); @@ -564,6 +586,9 @@ internal static class Program 0x80070032 => "ERROR_NOT_SUPPORTED — the installed WSL does not implement this call " + "(almost always: WSL is older than the SDK)", 0x80070005 => "E_ACCESSDENIED", + 0x800700B7 => "ERROR_ALREADY_EXISTS — a session of that name is already running", + 0x8000000E => "E_ILLEGAL_METHOD_CALL — the object is not in a state to serve this call " + + "(e.g. Session.Start() was never called, or failed)", 0x80040601 => "WSLC_E_IMAGE_NOT_FOUND", 0x80040603 => "WSLC_E_CONTAINER_NOT_FOUND", 0x80040605 => "WSLC_E_CONTAINER_NOT_RUNNING",