139 lines
9.0 KiB
Markdown
139 lines
9.0 KiB
Markdown
# M1 spikes (docs/WINDOWS_PORT.md §13)
|
|
|
|
Programs that answer questions the port is currently *guessing* at. They are checked in and
|
|
runnable on purpose: a spike whose answer nobody can reproduce six months later is a rumour.
|
|
|
|
They are deliberately **not** in `windows/Nucleic.sln`. The solution's broker contract tests must
|
|
keep running on any machine against `FakeWslc`; these need the `Microsoft.WSL.Containers` preview
|
|
NuGet and a real WSL stack, so they are built by path.
|
|
|
|
---
|
|
|
|
## `WslcApiDump` — what does the wslc API actually look like?
|
|
|
|
**Status: it has served its original purpose (docs/WINDOWS_PORT.md §13.1).** The real API is
|
|
known, and it was obtained without a Windows machine at all — the package is public, so the
|
|
`.nupkg` was downloaded, its projection assembly extracted, and its metadata read with
|
|
`MetadataLoadContext`. What this tool is *for* has therefore changed: its assumption list now
|
|
describes the surface actually observed in 2.9.3, so running it says **what the next package
|
|
version moved**, not what we guessed wrong.
|
|
|
|
**Why it is still reflection.** Same reason as before: a typed check fails to compile on the
|
|
first rename and reports one problem, where this reports all of them at once. That property is
|
|
worth keeping for a preview API whose next version can break anything.
|
|
|
|
Two facts it discovered that anything referencing this package needs:
|
|
|
|
- the assembly is **`wslcsdkcs.dll`**, not `Microsoft.WSL.Containers.dll` — loading it by package
|
|
name fails;
|
|
- the only published version is **2.9.3**, targeting `net8.0-windows10.0.19041.0`. The
|
|
`0.1.0-preview.1` pin this repo carried could never have restored.
|
|
|
|
```powershell
|
|
cd windows/spikes/WslcApiDump
|
|
dotnet run # dump the API + check every facade assumption
|
|
dotnet run -- --probe # + GetMissingComponents / GetVersion
|
|
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
|
|
```
|
|
|
|
**If it reports missing components**, the machine cannot run wslc yet, and the two components are
|
|
NOT fixed the same way — the tool prints the specific remedy for each:
|
|
|
|
| Missing | Fix |
|
|
| --- | --- |
|
|
| `VirtualMachinePlatform` | `wsl --install`, then **reboot** (an OS optional feature). |
|
|
| `WslPackage` | `wsl --update --pre-release`, then `wsl --shutdown`. WSL is installed but older than the SDK, and **2.9.3 is pre-release-only — a plain `wsl --update` will not get there.** Confirm with `wsl --version`. |
|
|
| `SdkNeedsUpdate` | The NuGet pin is ahead of the installed service: update WSL further, or pin the package back. |
|
|
|
|
The distinction is worth knowing because the failures look alike but mean opposite things:
|
|
`REGDB_E_CLASSNOTREG` (0x80040154) is *nothing installed*, `ERROR_NOT_SUPPORTED` (0x80070032) is
|
|
*installed but too old*. The tool decodes both, along with the `WSLC_E_*` range from `wslc.idl`,
|
|
because these COM exceptions often carry an empty message and leave nothing but a hex code.
|
|
`--session` is skipped while components are missing rather than failing the same way.
|
|
|
|
Note that the assumption check reads the **`Microsoft.WSL.Containers`** namespace only. That is
|
|
correctness, not tidiness: a C#/WinRT projection also exports `ABI.Microsoft.WSL.Containers.*`
|
|
marshalling types with the *same short names*, and matching on short name alone checks every
|
|
member against the marshalling struct — which reported 42 false MISSINGs, with `CreateMarshaler`
|
|
helpfully offered as the nearest name.
|
|
|
|
It writes the full public object model to `wslc-api-dump.txt` (`--out` to relocate) and prints an
|
|
`ok` / `MISSING` line per assumption, each with *why that member matters* and a nearest-name hint.
|
|
It exits 0 even when assumptions fail — a mismatch is the product, not an error. Only a genuinely
|
|
broken run (the assembly won't load) exits non-zero.
|
|
|
|
`--probe` has been **run clean on real hardware** (Windows 11 amd64, WSL 2.9.3): 54/54 `ok`,
|
|
no missing components. Every other path is exercised against a stand-in assembly carrying the
|
|
observed 2.9.3 shape — the `ABI.` shadow types, a service reporting missing components, an
|
|
`0x80070032` with an empty message, a `ServiceVersion` with no `ToString()` override, and a
|
|
duplicate-name `Session` throwing `0x80040607` — so a failure on your machine is a finding about
|
|
wslc, not about this tool.
|
|
|
|
Note the `ToString()` one, because it bit: a WinRT projection class does not override
|
|
`ToString()`, so printing a returned object gives you its *type name*. Values are rendered by
|
|
their properties instead — `ServiceVersion { Major=2, Minor=9, Revision=3 }`, not
|
|
`Microsoft.WSL.Containers.ServiceVersion`.
|
|
|
|
**What to send back:** the console output, and `wslc-api-dump.txt` if anything is MISSING —
|
|
which now means the package moved under us, not that we guessed wrong.
|
|
|
|
### Why `--session` earns its risk
|
|
|
|
`--session` creates a real wslc session named `nucleic-spike` under
|
|
`%LOCALAPPDATA%\Nucleic\spike\wslc`, starts it, and then **creates a second one with the same
|
|
name**. That second construction is the whole point, and it is the last open question D13 turns
|
|
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.
|
|
|
|
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`.
|
|
|
|
### The three answers that change the design
|
|
|
|
Most mismatches are a one-line edit in `WslcFacade.cs` — that is exactly what the `IWslc` seam is
|
|
for, and nothing above it should move. These three are different:
|
|
|
|
| Finding | Consequence |
|
|
| --- | --- |
|
|
| No container enumeration, stats, pty or attach in the SDK | **Settled by D13.** `wslcsdk.dll` wraps `WSLCCompat.idl` (the stable SDK surface), which genuinely lacks them; they all exist on `wslc.idl`, the service-internal COM interface `wslc.exe` calls — `ListContainers`, `Stats`, `ResizeTty`, `OpenContainer`/`Attach`, `OpenSessionByName`, and `IWSLCVirtualMachine::GetId`. The broker binds both surfaces; no CLI. |
|
|
| No create-or-attach on `Session` | **Answered:** `IWSLCSessionManager::OpenSessionByName` / `EnterSession` / `ListSessions` on the internal interface. §2.3 reattach has its mechanism. |
|
|
| No gateway address anywhere on the API | **Skip TCP:** `IWSLCVirtualMachine::GetId` returns the VM GUID, so §5's AF_HYPERV/AF_VSOCK path (true vsock parity with macOS) should become primary at M1 (b). Gateway TCP stays as fallback, its address from `GetAdaptersAddresses` over `vEthernet (WSL)`. |
|
|
| No uid on `ProcessSettings` | Recoverable, and already planned for: exec wraps argv in `setpriv`/`su agent -c`. Interceptors and nash don't care about the numeric uid (§3.2). |
|
|
|
|
---
|
|
|
|
## Not yet written
|
|
|
|
- **`WslcSpike`** — the typed happy path: session → GHCR pull of `naros-agent` → container with an
|
|
NTFS `ContainerVolume` → `exec git status` in the bind-mounted worktree → stdio round-trip →
|
|
SIGTERM, plus the 9P latency numbers §15 wants (`git status` and `npm install` on a real repo,
|
|
mounted vs. in-VM). Deliberately held back until `WslcApiDump` has run: written now, against
|
|
guessed names, it would not compile, and fixing it blind is the mistake this whole approach
|
|
exists to avoid.
|
|
- **`HvSocketSpike`** — M1 (b), and now the *primary* control-plane spike rather than the
|
|
fallback one (D13): bind an AF_HYPERV listener on the VM GUID from
|
|
`IWSLCVirtualMachine::GetId` and dial AF_VSOCK from inside a wslc container. If vsock
|
|
traverses the container's namespaces, the Windows control plane gets true parity with macOS
|
|
and §5's firewall/NAT variance stops mattering. Measure gateway-TCP reachability in the same
|
|
run so the fallback stays evidenced.
|