Repository navigation
spike: drive wslservice over COM instead of spawning wsl.exe - #379
Merged
Merged
Conversation
The spike #356 asks for before any implementation: establish which operations have a COM equivalent, whether elevation is needed, and how stable the surface is. Verdict GO. Measured on Windows 10 Pro 19045, WSL 2.9.11.0 Store, standard non-admin user, three runs with the first call discarded: wsl.exe -l -v mean 62-67 ms per call EnumerateDistributions mean 0.66-0.81 ms per call Roughly 85-90x. That is the difference between a status poll costing a process spawn and costing a function call. The interface is read from wslservice.idl in the open-sourced WSL rather than guessed: CLSID_LxssUserSession, IID_ILxssUserSession, and 24 methods after IUnknown, seven of which map onto internal/wsl.WSL. Struct layouts confirmed against the running service. Two findings the implementation depends on. No elevation is needed, but IMPERSONATION is: the first call returns ERROR_BAD_IMPERSONATION_LEVEL until the client calls CoInitializeSecurity with RPC_C_IMP_LEVEL_IMPERSONATE. The service impersonates the caller to act on that user's distros. runtime.LockOSThread is MANDATORY. Without it CoCreateInstance failed in 2 of 4 runs with CO_E_NOTINITIALIZED, because a COM apartment belongs to an OS thread and Go moves goroutines between threads freely. The failures clustered right after the process-spawn loop, which is when the scheduler has reason to migrate. Load-dependent, invisible in a quick test, and it would reach a user as an unexplained supervisor error. With the lock, 6 of 6 clean. Deliberately not established, and the README says so: only EnumerateDistributions was actually called -- CreateLxProcess carries handles and a process lifecycle across the RPC boundary and must not be assumed from this; one host and one WSL version, so the version gate and doctor drift check in #356 are the price of using this at all; and the in-box CLSID returned E_NOINTERFACE on this Store-WSL machine, so that flavour is untested. Refs #356
This was referenced Sep 17, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Refs #356 — the spike that issue asks for before any implementation.
Verdict: GO, with one hard requirement and one open risk
Measured on Windows 10 Pro 19045, WSL 2.9.11.0 (Store), standard non-admin user, three runs, first call discarded:
wsl.exe -l -vEnumerateDistributionsRoughly 85–90×. That is the difference between a status poll costing a process spawn and costing a function call — and the supervisor,
status,doctorand the health check all pay it today.The interface is read, not guessed
From
wslservice.idlin the open-sourced WSL:24 methods after
IUnknown; seven map ontointernal/wsl.WSL.Statushas no equivalent and stays on the CLI. Struct layouts confirmed against the running service (LXSS_ENUMERATE_INFO= 544 bytes,LXSS_ERROR_INFO= 40).Two findings the implementation depends on
1. No elevation, but impersonation is required.
CoCreateInstancesucceeds as a standard user; the first call then fails:The service impersonates the caller to act on that user's distros.
CoInitializeSecurity(..., RPC_C_IMP_LEVEL_IMPERSONATE, ...)once per process fixes it.2.
runtime.LockOSThreadis mandatory — and its absence is intermittent. Without it,CoCreateInstancefailed in 2 of 4 runs withCO_E_NOTINITIALIZED, because a COM apartment belongs to an OS thread and Go moves goroutines between threads freely. The failures clustered immediately after the process-spawn benchmark loop, which is exactly when the scheduler has reason to migrate.Load-dependent, invisible in a quick test, and it would reach a user as an unexplained supervisor error. With the lock: 6 of 6 clean. This is the single most important thing for the implementation to get right, and I would not have found it without benchmarking in the same process.
What is deliberately NOT established
EnumerateDistributionswas actually called.CreateLxProcessis the hard one — handles, pipes and a process lifecycle across an RPC boundary — and it is whatExec/Startneed. Nothing here should be assumed about it.doctordrift check internal/wsl: COM for the GUID-taking wslservice calls (Exec is out of scope) #356 calls for are not extras; they are the price of using this at all.LxssUserSessionInBoxreturnedE_NOINTERFACEon this Store-WSL machine, so the two CLSIDs are not interchangeable and that flavour is a real gap.Scope
Spike only — a throwaway module under
spike/d/, kept compiling by the existing CI step, with no change tointernal/wsl. The secondBackendimplementation is the follow-on work, and #356 stays open for it.