Repository navigation
internal/wsl: COM for the GUID-taking wslservice calls (Exec is out of scope) #356
Description
Activity
- added a commit that references this issue
on Sep 17, 2026 Spike done and merged as #379 (
spike/d/). Verdict: GO.Answering the three "to establish first" questions:
1. Which operations have a COM equivalent
All but one of the ones that matter. From
wslservice.idl—ILxssUserSession, 24 methods afterIUnknown:internal/wsl.WSLCOM method vtable slot ListEnumerateDistributions15 TerminateTerminateDistribution7 UnregisterUnregisterDistribution8 ImportRegisterDistribution4 ExportExportDistribution18 Exec/StartCreateLxProcess16 wsl --shutdownShutdown23 Statusnone — stays on the CLI — 2. Does it need elevation
No.
CoCreateInstancesucceeds as a standard user. But it does need impersonation: the first call returns0x80070542(Win32 1346,ERROR_BAD_IMPERSONATION_LEVEL) until the client callsCoInitializeSecuritywithRPC_C_IMP_LEVEL_IMPERSONATE. The service impersonates the caller to act on that user's distros.3. How stable is it
Unknown, and the spike does not pretend otherwise — one host, one WSL version (2.9.11.0 Store). But one concrete instability already showed up:
LxssUserSessionInBoxreturnedE_NOINTERFACEon this machine. The two CLSIDs are not interchangeable, so capability detection has to pick, and in-box WSL is untested.So the version gate and the
doctordrift check in this issue are not extras — they are the price of using this at all.The benchmark
Three runs, first call discarded, Windows 10 Pro 19045:
wsl.exe -l -vEnumerateDistributionsrun 1 66.9 ms 0.81 ms run 2 62.6 ms 0.74 ms run 3 66.0 ms 0.66 ms ~85–90×.
One finding that is not in the issue, and matters more than the speed
runtime.LockOSThreadis mandatory. Without itCoCreateInstancefailed in 2 of 4 runs withCO_E_NOTINITIALIZED— a COM apartment belongs to an OS thread, and Go moves goroutines between threads freely. The failures clustered immediately after the benchmark's process-spawn loop, i.e. exactly when the scheduler had reason to migrate.Load-dependent, invisible in a quick test, and on a user's machine it would present as an unexplained supervisor error. With the lock: 6 of 6 clean. It only surfaced because the CLI benchmark ran in the same process as the COM calls.
Left for the implementation
CreateLxProcessis untouched. It carries handles, pipes and a process lifecycle across the RPC boundary, and it is whatExec/Startneed. Nothing in this spike should be taken as evidence about it — it is the hard half.- In-box WSL flavour.
- The version gate, the
doctordrift check, and keeping the CLI implementation as the reference with its tests.
#408 landed the tractable part and closed the question the issue was really asking.
Execis out of scope, with a reason. ReadingCreateLxProcessinwslservice.idl: 24 parameters, four returned sockets (stdin, stdout, stderr,CommunicationChannel), a separateInteropSocket, a process handle and a server handle. Driving it means reimplementing the relay and channel protocolwsl.exealready implements, against an interface whose stability Microsoft disclaims, for calls that are not on a hot loop. Recorded in theFastdoc comment.What landed:
GetDistributionId(0.54 ms) andTerminateDistribution. Terminate goes ~99 ms → 10-16 ms, but ~15 ms of that is the service genuinely stopping the distro, so the saving is ~89 ms of process spawn and nothing more. Marginal as latency — it is not a hot path.The durable value is
GetDistributionId: every GUID-taking method needs it, so #381, #382 and #383 now have their lookup.A bug worth recording, caught only by the live test: a service-level error ("no such distro") was being read as "the COM surface has moved", which demoted COM for the whole process and re-ran the doomed call through
wsl.exe.ServiceErrornow separates the two.Suggest narrowing or closing this. The Exec/Start ambition is settled as "no"; the remaining GUID-taking surface will be pulled in by #381/#382/#383 as each needs it, which is a better unit of work than a standing "port things to COM" ticket.
- changed the title
[-]internal/wsl: talk to wslservice over COM instead of spawning wsl.exe for every call[/-][+]internal/wsl: COM for the GUID-taking wslservice calls (Exec is out of scope)[/+]on Sep 18, 2026 Retitled to match what this actually is now. \Exec/\Start\ are settled as out of scope with a reason recorded in the \Fast\ doc comment (\CreateLxProcess: 24 parameters, four returned sockets, an undocumented \CommunicationChannel), so the title promising a general port was misleading.
What remains under it is the rest of the GUID-taking surface, now that \GetDistributionId\ exists at 0.54 ms. Given #381 is parked on a design decision and #382/#383 are closed as non-viable, there is currently no caller waiting on more of it — so this is a fine thing to leave open and pull from when one appears, rather than work to schedule.
Every WSL operation skrog performs is a
wsl.exespawn.internal/wslexists precisely to keep that behind one interface, which makes a second implementation cheap to try: talk towslserviceover COM directly.Why it is worth doing
status,doctorand the health check all pay it.wslkit's tray polling issue (tray: polling hawser status every 4s costs ~7% of a core, for a status dot #192, ~7% of a core for a status dot) is the same cost in the sibling project.cmd/skrogwexists largely to avoid a console window appearing. A COM client has no console to suppress.wsl.exeoutput, which is localised, UTF-16, and reworded between releases.doctorwould gain a real error code to map through theerrors.jsontable wslkit already generates (Spike B: session-0 / no-login WSL2 from a Windows service (GO/NO-GO) #3).Scope
Not the plugin API. #325 covers
WSLCCreateProcess/WSLCProcessGetFdfor the wslc root namespace, runs in-process insidewslserviceas SYSTEM, and is blocked on Authenticode signing. This issue is the ordinary distro backend, as a COM client — no HKLM registration, no signing, no ability to crash the service.To establish first
--shutdown, enumerate registrations. The health check and supervisor start/stop are the paths that matter; anything without an equivalent keeps the CLI implementation.wslc.idlexplicitly disclaims stability (wslc backend: session lifecycle — one persistent named session, idle termination, 3.3 s cold boot, ~750 MB per VM #323); assume the same here until shown otherwise.Risk, and the mitigation that has to ship with it
wsl --updatecan move this surface silently — the same failure mode already documented for the wslc backend's engine version. So:wsl.exeimplementation when the running WSL version is outside the tested range.doctorcheck that detects the surface having shifted, rather than letting it fail as an unexplained supervisor error on someone else's machine after a Tuesday update.Deliverables
internal/wsl: a secondBackendimplementation behind the existing interface, selected by capability detection.doctorcheck for surface drift, and a documented supported-version range.skrogwpath for operations that moved.Found while discussing what the open-sourced WSL source makes newly possible; filed alongside #93.